`T {}` 在 Scala 中做什么
What does `T {}` do in Scala
浏览 Shapeless 代码时,我遇到了这个看似无关的 {}
here and 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
我不知道它的作用。有人可以解释一下吗?
任何类型后面都可以跟{}
封闭的类型和抽象非类型成员定义序列。这被称为 "refinement" 并且用于提供比正在优化的基本类型更高的精度。在实践中,细化最常用于表达对被细化类型的抽象类型成员的约束。
这个序列允许为空是一个鲜为人知的事实,在无形源代码中可以看到的形式中,T {}
是具有空细化的类型 T
.任何空的细化都是……空的……所以不会向细化类型添加任何额外的约束,因此类型 T
和 T {}
是等价的。我们可以让 Scala 编译器像这样为我们验证,
scala> implicitly[Int =:= Int {}]
res0: =:=[Int,Int] = <function1>
那我为什么要在 shapeless 做这种看似毫无意义的事情呢?这是因为改进的存在和类型推断之间的相互作用。如果您查看 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
(即 [=21 的单例类型=]),但是,除非明确要求,否则它不会推断出更精确的类型。相反,它推断出非单例(即加宽)类型 Foo
正如您在 f1
.
的情况下所见
但是在 Witness
in shapeless 的情况下,我明确地 想要 单例类型被推断用于 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
正如您在上面的 REPL 脚本中看到的,在第一种情况下,s1
被指定为加宽类型 String
,而 s2
被指定为单例类型 String("foo")
.
这是 SLS 强制要求的吗?不,但它与它是一致的,并且它具有某种意义。 Scala 的大部分类型推断机制都是实现定义的,而不是规范的,这可能是最不令人惊讶和最有问题的实例之一。
浏览 Shapeless 代码时,我遇到了这个看似无关的 {}
here and 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
我不知道它的作用。有人可以解释一下吗?
任何类型后面都可以跟{}
封闭的类型和抽象非类型成员定义序列。这被称为 "refinement" 并且用于提供比正在优化的基本类型更高的精度。在实践中,细化最常用于表达对被细化类型的抽象类型成员的约束。
这个序列允许为空是一个鲜为人知的事实,在无形源代码中可以看到的形式中,T {}
是具有空细化的类型 T
.任何空的细化都是……空的……所以不会向细化类型添加任何额外的约束,因此类型 T
和 T {}
是等价的。我们可以让 Scala 编译器像这样为我们验证,
scala> implicitly[Int =:= Int {}]
res0: =:=[Int,Int] = <function1>
那我为什么要在 shapeless 做这种看似毫无意义的事情呢?这是因为改进的存在和类型推断之间的相互作用。如果您查看 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
(即 [=21 的单例类型=]),但是,除非明确要求,否则它不会推断出更精确的类型。相反,它推断出非单例(即加宽)类型 Foo
正如您在 f1
.
但是在 Witness
in shapeless 的情况下,我明确地 想要 单例类型被推断用于 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
正如您在上面的 REPL 脚本中看到的,在第一种情况下,s1
被指定为加宽类型 String
,而 s2
被指定为单例类型 String("foo")
.
这是 SLS 强制要求的吗?不,但它与它是一致的,并且它具有某种意义。 Scala 的大部分类型推断机制都是实现定义的,而不是规范的,这可能是最不令人惊讶和最有问题的实例之一。