Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What does `T {}` do in Scala

Browsing Shapeless code, I came across this seemingly extraneous {} here and here:

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

I almost ignored it as a typo since it does nothing but apparently it does something. See this commit: https://github.com/milessabin/shapeless/commit/56a3de48094e691d56a937ccf461d808de391961

I have no idea what it does. Can someone explain?

like image 487
pathikrit Avatar asked Mar 09 '16 21:03

pathikrit


People also ask

What is T type in Scala?

Here we have defined an abstract type T . It is used to describe the type of element . We can extend this trait in an abstract class, adding an upper-type-bound to T to make it more specific.

What is def keyword in Scala?

def is the keyword you use to define a method, the method name is double , and the input parameter a has the type Int , which is Scala's integer data type. The body of the function is shown on the right side, and in this example it simply doubles the value of the input parameter a : def double(a: Int) = a * 2 -----


1 Answers

Any type can be followed by a {} enclosed sequence of type and abstract non-type member definitions. This is known as a "refinement" and is used to provide additional precision over the base type that is being refined. In practice refinements are most commonly used to express constraints on abstract type members of the type being refined.

It's a little known fact that this sequence is allowed to be empty, and in the form that you can see in the shapeless source code, T {} is the type T with an empty refinement. Any empty refinement is ... empty ... so doesn't add any additional constraints to the refined type and hence the types T and T {} are equivalent. We can get the Scala compiler to verify that for us like so,

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

So why would I do such an apparently pointless thing in shapeless? It's because of the interaction between the presence of refinements and type inference. If you look in the relevant section of the Scala Language Specification you will see that the type inference algorithm attempts to avoid inferring singleton types in at least some circumstances. Here is an example of it doing just that,

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 

As you can see from the definition of f2 the Scala compiler knows that the value foo has the more precise type foo.type (ie. the singleton type of val foo), however, unless explicitly requested it won't infer that more precise type. Instead it infers the non-singleton (ie. widened) type Foo as you can see in the case of f1.

But in the case of Witness in shapeless I explicitly want the singleton type to be inferred for uses of the value member (the whole point of Witness is enable us to pass between the type and value levels via singleton types), so is there any way the Scala compiler can be persuaded to do that?

It turns out that an empty refinement does exactly that,

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 

As you can see in the above REPL transcript, in the first case s1 has been typed as the widened type String whereas s2 has been assigned the singleton type String("foo").

Is this mandated by the SLS? No, but it is consistent with it, and it makes some sort of sense. Much of Scala's type inference mechanics are implementation defined rather than spec'ed and this is probably one of the least surprising and problematic instances of that.

like image 193
Miles Sabin Avatar answered Oct 02 '22 01:10

Miles Sabin