scala shapeless

¿Qué hace `T{}` en Scala



shapeless (1)

Cualquier tipo puede ir seguido de una {} secuencia adjunta de tipo y definiciones abstractas de miembros que no sean de tipo. Esto se conoce como "refinamiento" y se usa para proporcionar precisión adicional sobre el tipo de base que se está refinando. En la práctica, los refinamientos se usan más comúnmente para expresar restricciones en los miembros de tipo abstracto del tipo que se refina.

Es un hecho poco conocido que se permite que esta secuencia esté vacía, y en la forma que se puede ver en el código fuente informe, T {} es el tipo T con un refinamiento vacío. Cualquier refinamiento vacío está ... vacío ... por lo tanto, no agrega ninguna restricción adicional al tipo refinado y, por lo tanto, los tipos T y T {} son equivalentes. Podemos hacer que el compilador de Scala verifique eso para nosotros así,

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

Entonces, ¿por qué iba a hacer algo tan aparentemente sin sentido sin forma? Se debe a la interacción entre la presencia de refinamientos y la inferencia tipográfica. Si mira en scala-lang.org/files/archive/spec/2.11/… de la Especificación de idioma de Scala, verá que el algoritmo de inferencia de tipo intenta evitar inferir tipos de singleton en al menos algunas circunstancias. Aquí hay un ejemplo de que hace exactamente eso,

scala> class Foo ; val foo = new Foo defined class Foo foo: Foo = Foo@8bd1b6a scala> val f1 = foo f1: Foo = Foo@8bd1b6a scala> val f2: foo.type = foo f2: foo.type = Foo@8bd1b6a

Como se puede ver en la definición de f2 el compilador de Scala sabe que el valor foo tiene el tipo más preciso foo.type (es decir, el tipo singleton de val foo ), sin embargo, a menos que se solicite explícitamente, no inferirá ese tipo más preciso . En su lugar, infiere el tipo Foo no único (es decir, ampliado) como puede ver en el caso de f1 .

Pero en el caso de Witness in formless, explícitamente quiero que el tipo singleton se infiera para los usos del miembro value (el punto entero de Witness es permitirnos pasar entre los niveles tipo y valor a través de tipos singleton), ¿hay alguna manera? el compilador Scala puede ser persuadido a hacer eso?

Resulta que un refinamiento vacío hace exactamente eso,

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

Como puede ver en la transcripción REPL anterior, en el primer caso, s1 se tipeó como el tipo ensanchado String mientras que a s2 se le asignó el tipo singleton String("foo") .

¿Esto es obligatorio por el SLS? No, pero es consistente con eso, y tiene algún tipo de sentido. Gran parte de la mecánica de inferencia de tipos de Scala se define en la implementación en lugar de especificada y esta es probablemente una de las instancias menos sorprendentes y problemáticas de eso.

Al navegar por el código Shapeless, me encontré con este aparentemente extraño {} here y here :

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

Casi lo ignoré como un error tipográfico ya que no hace nada pero aparentemente hace algo. Consulte este compromiso: https://github.com/milessabin/shapeless/commit/56a3de48094e691d56a937ccf461d808de391961

No tengo idea de lo que hace. ¿Alguien puede explicar?