Scala中`T {}`做了什么

pat*_*rit 40 scala shapeless

浏览无形代码,我在{} 这里和这里看到了看似无关紧要的代码:

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

trait SingletonOps {
  import record._
  type T
  def narrow: T {} = witness.value
}
Run Code Online (Sandbox Code Playgroud)

我几乎忽略了它作为一个错字,因为它什么也没做,但显然它做了一些事情.看到这个提交:https://github.com/milessabin/shapeless/commit/56a3de48094e691d56a937ccf461d808de391961

我不知道它做了什么.谁能解释一下?

Mil*_*bin 54

任何类型都可以跟随一个{}封闭的类型序列和抽象的非类型成员定义.这被称为"细化",用于为正在改进的基本类型提供额外的精度.在实践中,细化最常用于表示对要改进的类型的抽象类型成员的约束.

一个鲜为人知的事实是,允许此序列为空,并且您可以在无形源代码中看到的形式T {}是T具有空细化的类型.任何空的细化是......空......所以不添加任何额外的限制,以精致的类型,因此类型T和T {}是等价的.我们可以让Scala编译器为我们验证这样,

scala> implicitly[Int =:= Int {}]
res0: =:=[Int,Int] = <function1>
Run Code Online (Sandbox Code Playgroud)

那么为什么我会在无形的情况下做这样一个毫无意义的事呢?这是因为改进和类型推断的存在之间的相互作用.如果查看Scala语言规范的相关部分,您将看到类型推断算法尝试在至少某些情况下尝试避免推断单例类型.这是一个这样做的例子,

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
Run Code Online (Sandbox Code Playgroud)

正如您从f2Scala编译器的定义中所看到的那样,知道该值foo具有更精确的类型foo.type(即单例类型val foo),但是,除非明确请求,否则它不会推断出更精确的类型.相反,它推断出非单例(即加宽)类型,Foo如您所见f1.

但是在无Witness形状的情况下,我明确地希望推断单元类型用于value成员的使用(整个点Witness是使我们能够通过单例类型在类型和值级别之间传递),所以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
Run Code Online (Sandbox Code Playgroud)

正如您在上面的REPL记录中所看到的,在第一种情况下,s1已经将其键入为加宽类型String,s2而已为其分配了单例类型String("foo").

这是否由SLS强制执行?不,但它与它一致,并且它具有某种意义.Scala的大部分类型推理机制都是实现定义而不是spec'ed,这可能是最不令人惊讶和有问题的实例之一.

  • 就我而言,完全是偶然的。 (3认同)