浏览无形代码,我遇到了这个看似无关的{}
here和here:
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
我不知道它做了什么。有人可以解释一下吗?
答案 0 :(得分:53)
任何类型后面都可以跟一个{}
封闭的类型序列和抽象的非类型成员定义。这被称为“细化”,用于提供与正在细化的基本类型相比的额外精度。在实践中,细化最常用于表示对要细化的类型的抽象类型成员的约束。
一个鲜为人知的事实是,允许此序列为空,并且在无形源代码中可以看到的形式中,T {}
是具有空细化的类型T
。任何空的细化都是......空的...因此不会向细化类型添加任何其他约束,因此类型T
和T {}
是等效的。我们可以让Scala编译器为我们验证,
scala> implicitly[Int =:= Int {}]
res0: =:=[Int,Int] = <function1>
那么为什么我会在无形的情况下做这样一个毫无意义的事呢?这是因为改进和类型推断的存在之间的相互作用。如果你查看Scala语言规范的the relevant section,你会发现类型推断算法试图避免在至少某些情况下推断单例类型。这是一个这样做的例子,
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
从f2
的定义可以看出,Scala编译器知道值foo
具有更精确的类型foo.type
(即val foo
的单例类型但是,除非明确要求,否则不会推断出更精确的类型。相反,它可以推断非单例(即加宽)类型Foo
,如f1
中所示。
但是在Witness
无形状的情况下,我明确地想要推断使用value
成员的单例类型({{1}的整点使我们能够通过单例类型在类型和值级别之间传递),那么有没有什么方法可以说服Scala编译器这样做呢?
事实证明,一个空的细化正是如此,
Witness
正如您在上面的REPL记录中所看到的,在第一种情况下,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
已被输入为加宽类型s1
,而String
已被指定为单例类型{{1} }。
这是否由SLS强制执行?不,但它与它一致,并且它具有某种意义。 Scala的大部分类型推理机制都是实现定义而不是spec'ed,这可能是最不令人惊讶和有问题的实例之一。