Scala:隐式参数解析优先级

11 浏览
0 Comments

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.xImportIntFooLocalIntFoo 的名称不相同。

第三,我可以展示名称绑定优先级单独不起作用,如下所示:

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演示而不是规范。他的讲话是促使我调查这个问题的动力。

编译器实现

admin 更改状态以发布 2023年5月21日
0
0 Comments

来自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。

0
0 Comments

我写了一篇博客文章:重新审视无需导入税的隐式转换,来回答这个问题。

更新:此外,Martin Odersky在上述帖子中的评论揭示了Scala 2.9.1的行为,LocalIntFoo优先于ImportedIntFoo实际上是一个错误。 参见隐式参数优先级再次

  • 1)隐式转换在当前调用作用域内可见,通过局部声明,导入,外部作用域,继承,可以在没有前缀的情况下访问包对象。
  • 2)隐式转换作用域,其中包含各种与我们搜索的隐式类型相关的伴随对象和包对象(即类型的包对象,类型本身的伴随对象,类型构造函数的伴随对象,类型参数的伴随对象,以及其超类型和超特性的伴随对象)。

如果在任何阶段我们找到了多个隐式转换,则使用静态重载规则来解决它。

更新2:当我问Josh关于“无需导入税的隐式转换”时,他向我解释了他所指的名称绑定规则,这些规则适用于命名完全相同的隐式转换。

0