Что означает объявление типа без определения в объекте? - PullRequest
0 голосов
/ 11 января 2019

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 Ответ

0 голосов
/ 11 января 2019

Для метода (или значения) есть только 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?
}
...