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
вызовы выше компилируются просто отлично.
Поэтому, мой вопрос, какова цель таких объявлений типов? Какие проблемы они предназначены для решения и почему они не запрещены, как и другие виды абстрактных элементов в объектах?