scala 2.10 编译器中 scala.language.dynamics 错误的解决方法

Workaround for a scala.language.dynamics bug in scala 2.10 compiler

我一直在尝试为一个开源项目生成一个 SBT 版本,我想在我的项目中引用它,但我 运行 遇到了一个似乎是编译器错误的问题。

以下代码在 eclipse/scala-ide 中按预期编译和运行,但 scala 2.10.6 编译器无法消化它:

package foo

import scala.language.dynamics

object Caller extends App {
  val client = new Client() // initialise an R interpreter
  client.x = 1.0
}
class Client extends Dynamic {
  var map = Map.empty[String, Any]
  def selectDynamic(name: String) = map get name getOrElse sys.error("field not found")
  def updateDynamic(name: String)(value: Any) { map += name -> value }
}

这是我的 build.sbt:

scalaVersion := "2.10.6"

libraryDependencies++= Seq(
  "org.scalanlp" %% "breeze" % "0.12"
)

当我指定 scalaVersion := 2.10.6 时,出现以下编译错误:

[error] /home/philwalk/dynsbt/src/main/scala/foo/Caller.scala:8: type mismatch;
[error]  found   : foo.Caller.client.type (with underlying type foo.Client)
[error]  required: ?{def x: ?}
[error] Note that implicit conversions are not applicable because they are ambiguous:
[error]  both method any2Ensuring in object Predef of type [A](x: A)Ensuring[A]
[error]  and method any2ArrowAssoc in object Predef of type [A](x: A)ArrowAssoc[A]
[error]  are possible conversion functions from foo.Caller.client.type to ?{def x: ?}
[error]   client.x = Seq("a","b","c")
[error]   ^
[error] one error found
[error] (compile:compileIncremental) Compilation failed
[error] Total time: 3 s, completed May 3, 2016 11:03:08 AM

使用 scalaVersion := 2.11.8,没问题,虽然我需要 cross-compile,所以那不是 work-around。

另一个线索是我可以通过更改这行代码来隐藏问题:

client.x = 1.0

对此:

client.xx = 1.0

我直接用scalac 2.10.6编译也看到了这个问题

作为解决方法,我可以重构项目以使用比单个字符长的字段名称,尽管因为这不是我的项目,所以我在某种程度上限制了我可以接受的解决方法。此外,这是一个 breeze.linalg 项目,如果不允许使用单个字符矩阵和向量名称将是一个严重的限制。

花了几个小时才将问题归结为来自一个较大项目的这段代码片段,我不想对这个开源库的 scala 2.10 版本强加限制。由于此错误似乎已在 scala 2.11 中修复,我假设已决定不将修复程序反向移植到 2.10。

我更改了标题以反映存在解决方法(更长的字段名称)。

这是 Scala 2.10 的错,不是 sbt 的错。

问题在于,在Predef, whose content is imported in every single Scala file, there are two problematic classes: Ensuring and ArrowAssoc。这两个 类 的成员可通过对 any 类型值的隐式转换获得。例如,ArrowAssoc 是您可以 1 -> 2 构造元组 (1, 2).

的原因

现在,这些 类 在 2.10 中非常不幸地 属性 声明了一个名为 x 的成员!即使在 2.10 中已弃用,scala.Dynamic.

的用法仍然存在严重问题

在您的代码中,client.x = 1.0 首先测试 client.x 是否作为 val/getter 存在于 client 的类型中。它确实不是,但是如果我们使用 Predef 中的隐式转换将其转换为 EnsuringArrowAssoc,它 可用。由于隐式转换比 selectDynamic 处理具有更高的优先级,因此 Scala 编译器会尝试使用它们。但是由于有两个同样有效的转换,它们是不明确的,你会得到一个编译错误。

总而言之,这是两个事实的不幸后果:

  • 存在隐式转换,在 everything
  • 上提供成员 x
  • 该转换优先于 Dynamic 处理。

在您的实例中,解决此问题的方法是在 Client 中显式声明 x,作为 selectDynamicupdateDynamic 的转发器:

class Client extends Dynamic {
  var map = Map.empty[String, Any]
  def selectDynamic(name: String) = map get name getOrElse sys.error("field not found")
  def updateDynamic(name: String)(value: Any) { map += name -> value }

  // Work around the annoying implicits in Predef in Scala 2.10.
  def x: Any = selectDynamic("x")
  def x_=(value: Any): Unit = updateDynamic("x")(value)
}

现在,client.x当然会使用显式声明的x及其在Client中的setterx_=,这将委托给selectDynamic/updateDynamic.