`T {}` 在 Scala 中有什么作用 [英] What does `T {}` do in Scala

查看:48
本文介绍了`T {}` 在 Scala 中有什么作用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

浏览 Shapeless 代码时,我遇到了这个看似无关紧要的 {} 此处这里:

trait Witness extends Serializable {T型val 值:T{}}特质 SingletonOps {导入记录._T型def 窄:T {} =witness.value}

我差点把它当作错别字忽略了,因为它什么也没做,但显然它做了一些事情.请参阅此提交:https://github.com/milessabin/shapeless/commit/56a3de48094e6937d59a3161d561a931d56a937cf461d808de391961

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

解决方案

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

这个序列允许为空是一个鲜为人知的事实,在你在无形源代码中看到的形式中,T{}就是T类型代码> 带有空的细化.任何空的细化都是......空......所以不会向细化类型添加任何额外的约束,因此类型 TT {} 是等效的.我们可以像这样让 Scala 编译器为我们验证,

scala>隐式[Int =:= Int {}]res0: =:=[Int,Int] = 

那我为什么要在无形中做这种看似毫无意义的事情?这是因为存在细化和类型推断之间的相互作用.如果您查看 相关部分 of the Scala Language Specification,您将看到类型推断算法至少在某些情况下试图避免推断单例类型.这是一个这样做的例子,

scala>类 Foo ;val foo = 新 Foo定义类 Foofoo: foo = foo@8bd1b6a标度>val f1 = foof1: Foo = Foo@8bd1b6a标度>val f2: foo.type = foof2: foo.type = Foo@8bd1b6a

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

但是对于 Witness in shapeless 我明确希望推断单例类型以用于 value 成员(整个Witness 的一点是使我们能够通过单例类型在类型和值级别之间传递),那么有什么方法可以说服 Scala 编译器这样做吗?

事实证明,一个空的细化就是这样做的,

scala>def窄[T <: AnyRef](t: T): t.type = t窄:[T <: AnyRef](t: T)t.type标度>val s1 = 窄("foo")//加宽s1: 字符串 = foo标度>def strict[T <: AnyRef](t: T): t.type {} = t//注意空细化窄:[T <: AnyRef](t: T)t.type标度>val s2 = strict("foo")//未加宽s2: String("foo") = foo

正如你在上面的 REPL 脚本中看到的,在第一种情况下 s1 被输入为加宽类型 Strings2 有被分配了单例类型String("foo").

这是 SLS 规定的吗?不,但它与它一致,并且具有某种意义.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?

解决方案

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.

这篇关于`T {}` 在 Scala 中有什么作用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆