Что означает объявление типа без определения в объекте?
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?
}