Что означает объявление типа без определения в объекте?

Scala позволяет определять типы, используя type ключевое слово, которое обычно имеет немного различное значение и цель в зависимости от того, когда они объявлены.

Если вы используете type внутри объекта или объекта пакета вы бы определили псевдоним типа, то есть более короткое / понятное имя для другого типа:

package object whatever {
  type IntPredicate = Int => Boolean

  def checkZero(p: IntPredicate): Boolean = p(0)
}

Типы, объявленные в классах / признаках, обычно предназначены для переопределения в подклассах / подтрейтах, а также в конечном итоге разрешаются в конкретный тип:

trait FixtureSpec {
  type FixtureType
  def initFixture(f: FixtureType) = ...
}

trait SomeSpec extends FixtureSpec {
  override type FixtureType = String

  def test(): Unit = {
    initFixture("hello")
    ...
  }
}

Существуют и другие способы использования абстрактных объявлений типов, но в любом случае они в конечном итоге разрешаются для некоторых конкретных типов.

Однако есть также возможность объявить абстрактный тип (т.е. без фактического определения) внутри объекта:

object Example {
  type X
}

И это компилируется, в отличие, например, от абстрактных методов:

object Example {
  def method: String  // compilation error
}

Поскольку объекты не могут быть расширены, они никогда не могут быть преобразованы в конкретные типы.

Я предположил, что такие определения типов удобно использовать в качестве фантомных типов. Например (используя теговые типы Shapeless):

import shapeless.tag.@@
import shapeless.tag

type ++>[-F, +T]

trait Converter

val intStringConverter: Converter @@ (String ++> Int) = tag[String ++> Int](...)

Тем не менее, похоже, что система типов обрабатывает эти типы отличается от обычных типов, что приводит к сбою вышеупомянутого использования "абстрактных" типов в определенных сценариях.

В частности, при поиске неявных параметров Scala в конечном итоге изучает неявную область, связанную с "ассоциированными" типами, то есть типами, которые присутствуют в сигнатуре типа неявных параметров. Тем не менее, кажется, что есть некоторые ограничения на вложение этих связанных типов, когда используются "абстрактные" типы. Рассмотрим этот пример настройки:

import shapeless.tag.@@

trait Converter

type ++>[-F, +T]

case class DomainType()

object DomainType {
  implicit val converter0: Converter @@ DomainType = null
  implicit val converter1: Converter @@ Seq[DomainType] = null
  implicit val converter2: Converter @@ (Seq[String] ++> Seq[DomainType]) = null

}

// compiles
implicitly[Converter @@ DomainType]
// compiles
implicitly[Converter @@ Seq[DomainType]]
// fails!
implicitly[Converter @@ (Seq[String] ++> Seq[DomainType])]

Здесь первые два неявных разрешения компилируются просто отлично, в то время как последнее терпит неудачу с ошибкой об отсутствии неявного. Если я определяю неявное в той же области, что и implicitly вызовите, затем он компилирует:

implicit val converter2: Converter @@ (Seq[String] ++> Seq[DomainType]) = null
// compiles
implicitly[Converter @@ (Seq[String] ++> Seq[DomainType])]

Однако, если я изменю ++> определение быть trait скорее, чем type:

trait ++>[-F, +T]

тогда все implicitly вызовы выше компилируются просто отлично.

Поэтому мой вопрос заключается в том, какова цель таких объявлений типов? Какие проблемы они предназначены для решения и почему они не запрещены, как и другие виды абстрактных элементов в объектах?

1 ответ

Для метода (или значения) есть только 2 варианта: либо он имеет тело (и тогда он "конкретный"), либо его нет (тогда он "абстрактный"). Тип X всегда некоторый интервал типа X >: LowerBound <: UpperBound (и мы называем это конкретным, если LowerBound = UpperBound или полностью абстрактно, если LowerBound = Nothing, UpperBound = Any но есть множество случаев между ними). Поэтому, если мы хотим запретить абстрактные типы в объектах, у нас всегда должен быть способ проверить эти типы LowerBound а также UpperBound равны. Но они могут быть определены некоторым сложным способом, и обычно такая проверка может быть не такой простой:

object Example {
  type X >: N#Add[N] <: N#Mult[Two] // Do we expect that compiler proves n+n=n*2?
}
Другие вопросы по тегам