Scala:隐式参数解析优先级
Scala:隐式参数解析优先级
假设我们只涉及本地范围的隐式参数查找:
trait CanFoo[A] { def foos(x: A): String } object Def { implicit object ImportIntFoo extends CanFoo[Int] { def foos(x: Int) = "ImportIntFoo:" + x.toString } } object Main { def test(): String = { implicit object LocalIntFoo extends CanFoo[Int] { def foos(x: Int) = "LocalIntFoo:" + x.toString } import Def._ foo(1) } def foo[A:CanFoo](x: A): String = implicitly[CanFoo[A]].foos(x) }
在上面的代码中,LocalIntFoo
赢过 ImportedIntFoo
。
能否有人解释一下为什么使用“静态重载解析规则(§6.26.3)”认为它更具体?
编辑:
名称绑定优先级是一个有力的论据,但还有几个未解决的问题。
首先,Scala语言参考说:
如果有几个符合隐式参数类型的合适参数,则将使用静态重载解析规则(§6.26.3)选择最具体的一个。
其次,名称绑定优先级是关于解决已知标识符 x
到特定成员 pkg.A.B.x
的问题,如果作用域中有几个命名为 x
的变量/方法/对象,则将其解析为特定成员 pkg.A.B.x
。ImportIntFoo
和 LocalIntFoo
的名称不相同。
第三,我可以展示名称绑定优先级单独不起作用,如下所示:
trait CanFoo[A] { def foos(x: A): String } object Def { implicit object ImportIntFoo extends CanFoo[Int] { def foos(x: Int) = "ImportIntFoo:" + x.toString } } object Main { def test(): String = { implicit object LocalAnyFoo extends CanFoo[Any] { def foos(x: Any) = "LocalAnyFoo:" + x.toString } // implicit object LocalIntFoo extends CanFoo[Int] { // def foos(x: Int) = "LocalIntFoo:" + x.toString // } import Def._ foo(1) } def foo[A:CanFoo](x: A): String = implicitly[CanFoo[A]].foos(x) } println(Main.test)
将其放在 test.scala
中并运行 scala test.scala
,它将打印出 ImportIntFoo:1
。
这是因为静态重载解析规则(§6.26.3)表示最具体的类型胜利。
如果我们假装所有合格的隐式值都命名相同,则 LocalAnyFoo
应掩盖 ImportIntFoo
。
相关:
这是一个关于隐式参数解析的很好的总结,但它引用了Josh的nescala演示而不是规范。他的讲话是促使我调查这个问题的动力。
编译器实现
来自http://www.scala-lang.org/docu/files/ScalaReference.pdf,第2章:
Scala中的名称标识了类型、值、方法和类,统称为实体。这些名称由局部定义和声明(§4)、继承(§5.1.3)、导入子句(§4.7)或包子句(§9.2)引入,这些都被称为绑定。
不同类型的绑定由优先级定义:
1. 定义和声明,即局部定义、继承或在同一编译单元中通过包子句提供的声明具有最高优先级。
2. 显式导入次之。
3. 通配符导入次之。
4. 由不在声明出现的编译单元中的包子句提供的定义具有最低优先级。
我可能错了,但对foo(1)的调用在与LocalIntFoo相同的编译单元中,结果将优先使用该转换而不是ImportedIntFoo。
我写了一篇博客文章:重新审视无需导入税的隐式转换,来回答这个问题。
更新:此外,Martin Odersky在上述帖子中的评论揭示了Scala 2.9.1的行为,LocalIntFoo
优先于ImportedIntFoo
实际上是一个错误。 参见隐式参数优先级再次。
- 1)隐式转换在当前调用作用域内可见,通过局部声明,导入,外部作用域,继承,可以在没有前缀的情况下访问包对象。
- 2)隐式转换作用域,其中包含各种与我们搜索的隐式类型相关的伴随对象和包对象(即类型的包对象,类型本身的伴随对象,类型构造函数的伴随对象,类型参数的伴随对象,以及其超类型和超特性的伴随对象)。
如果在任何阶段我们找到了多个隐式转换,则使用静态重载规则来解决它。
更新2:当我问Josh关于“无需导入税的隐式转换”时,他向我解释了他所指的名称绑定规则,这些规则适用于命名完全相同的隐式转换。