Подтвердить что ты не робот

Что делает `T {}` в Scala

Просмотр безшовного кода, я наткнулся на это, казалось бы, постороннее {} здесь и здесь:

trait Witness extends Serializable {
  type T
  val value: T {}
}

trait SingletonOps {
  import record._
  type T
  def narrow: T {} = witness.value
}

Я почти проигнорировал его как опечатку, поскольку он ничего не делает, но, видимо, он что-то делает. См. Эту фиксацию: https://github.com/milessabin/shapeless/commit/56a3de48094e691d56a937ccf461d808de391961

Я понятия не имею, что он делает. Может кто-нибудь объяснить?

4b9b3361

Ответ 1

За любым типом может следовать заключенная в {} последовательность типов и абстрактных нетерриториальных определений элементов. Это известно как "уточнение" и используется для обеспечения дополнительной точности по базовому типу, который уточняется. На практике уточнения наиболее часто используются для выражения ограничений для элементов абстрактного типа уточняемого типа.

Немного известно, что этой последовательности разрешено быть пустым и в форме, которую вы видите в бесформенном исходном коде, T {} - это тип T с пустой обработкой. Любое пустое уточнение... пустое... поэтому не добавляет никаких дополнительных ограничений к уточненному типу, и, следовательно, типы T и T {} эквивалентны. Мы можем получить компилятор Scala, чтобы убедиться, что для нас так,

scala> implicitly[Int =:= Int {}]
res0: =:=[Int,Int] = <function1>

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

scala> class Foo ; val foo = new Foo
defined class Foo
foo: Foo = [email protected]

scala> val f1 = foo
f1: Foo = [email protected]

scala> val f2: foo.type = foo
f2: foo.type = [email protected]

Как видно из определения f2, компилятор Scala знает, что значение foo имеет более точный тип foo.type (т.е. одноэлементный тип val foo), однако, если явно попросил, чтобы он не сделал вывод о том, что более точный тип. Вместо этого он отображает неединичный (т.е. Расширенный) тип foo, как вы можете видеть в случае f1.

Но в случае Witness в бесформенном я явно хочу, чтобы одноэлементный тип был выведен для использования члена value (вся точка Witness позволяет нам проходить между уровнями и значениями через singleton), так есть ли способ, которым можно убедить компилятор Scala сделать это?

Оказывается, что пустое уточнение делает именно то, что

scala> def narrow[T <: AnyRef](t: T): t.type = t
narrow: [T <: AnyRef](t: T)t.type

scala> val s1 = narrow("foo")  // Widened
s1: String = foo

scala> def narrow[T <: AnyRef](t: T): t.type {} = t  // Note empty refinement
narrow: [T <: AnyRef](t: T)t.type

scala> val s2 = narrow("foo")  // Not widened
s2: String("foo") = foo

Как вы можете видеть в приведенном выше протоколе REPL, в первом случае s1 был введен как расширенный тип String, тогда как s2 был назначен одноэлементный тип String("foo").

Это предусмотрено SLS? Нет, но это согласуется с ним, и это имеет какой-то смысл. Большая часть механики вывода типа w60 > определяется реализацией, а не spec'ed, и это, вероятно, один из наименее удивительных и проблемных примеров.