【问题标题】:Scala implicit def do not work if the def name is toString如果 def 名称是 toString,Scala 隐式 def 不起作用
【发布时间】:2019-02-26 05:55:39
【问题描述】:

这段代码编译失败:

object Foo {
  implicit def toString(i: Int): String = i.toString      
  def foo(x: String) = println(x)
  foo(23)
}    

以上代码编译失败,出现以下错误:

error: type mismatch;
found   : scala.this.Int(23)
required: String
  foo(23)

但是,这段代码可以编译

object Foo {
  implicit def asString(i: Int): String = i.toString      
  def foo(x: String) = println(x)
  foo(23)
}

为什么implicit def 的名称很重要?

注意: 如果方法被命名为equals,它也不起作用,但如果它被命名为hashCodeclone 等,它会起作用。

【问题讨论】:

  • 错误是什么?
  • found: Int; required: String

标签: scala implicit


【解决方案1】:

这里的问题不在于toStringFoo 上超载,正如其他(现已删除)答案之一所说的那样(您可以尝试类似地重载asString,它会起作用),而是您正在导入的 toString 与封闭类的 toString 冲突(在您的情况下,是由 REPL 组成的一些合成对象)。

我认为以下无隐式示例(也不使用“内置”方法名称,如 toString)更清楚地说明了这个问题:

class Foo {
  def asString(i: Int): String = "this is the one from Foo!"
}

class Bar {
  def asString(i: Int): String = "this is the one from Bar!"
}

object Demo extends Bar {
  val instance = new Foo
  import instance._

  println(asString(23))
}

这将使用来自BarasString,即使您可能认为导入的会优先:

scala> Demo
this is the one from Bar!
res1: Demo.type = Demo$@6987a133

事实上,它会使用来自Bar 的定义,即使参数没有对齐:

class Foo {
  def asString(i: Int): String = "this is the one from Foo!"
}

class Bar {
  def asString(): String = "this is the one from Bar!"
}

object Demo extends Bar {
  val instance = new Foo
  import instance._

  println(asString(23))
}

编译失败:

<pastie>:25: error: no arguments allowed for nullary method asString: ()String
  println(asString(324))
                   ^

现在我们可以让它看起来更像您的原始代码:

class Foo {
  implicit def asString(i: Int): String = "this is the one from Foo!"
  def foo(s: String): String = s
}

class Bar {
  def asString(): String = "this is the one from Bar!"
}

object Demo extends Bar {
  val instance = new Foo
  import instance._

  println(foo(23))
}

由于相同的原因,此操作失败并出现与您看到的相同的错误:导入的隐式转换被封闭类中具有相同名称的定义隐藏。

脚注 1

你问了以下问题:

为什么implicit def 的名称很重要?

隐含的名称​​一直很重要。这就是语言的工作方式。例如:

scala> List(1, 2, 3) + ""
res0: String = List(1, 2, 3)

scala> trait Garbage
defined trait Garbage

scala> implicit val any2stringadd: Garbage = new Garbage {}
any2stringadd: Garbage = $anon$1@5b000fe6

scala> List(1, 2, 3) + ""
<console>:13: error: value + is not a member of List[Int]
       List(1, 2, 3) + ""
                     ^

我们所做的是定义了一个隐式值,它隐藏了scala.Predef 中的any2stringadd 隐式转换。 (是的,这有点可怕。)

脚注 2

我认为这里可能存在编译器错误,至少就错误消息而言。如果你在我上面的第二个版本中稍微改变一下,例如:

class Foo {
  def asString(i: Int): String = "this is the one from Foo!"
}

class Bar {
  def asString(): String = "this is the one from Bar!"
}

object Demo extends Bar {
  def test(): Unit = {
    val instance = new Foo
    import instance._

    println(asString(23))
  }
}

……你会得到一个更合理的信息:

<pastie>:26: error: reference to asString is ambiguous;
it is both defined in class Bar and imported subsequently by
import instance._
    println(asString(23))
            ^

在我看来,这几乎肯定是编译器应该在你原来的情况下告诉你的事情。我也不确定为什么要考虑隐藏隐式进行转换,但它是,因为您可以判断您是否使用-Xlog-implicits 在 REPL 中运行您的代码:

scala> foo(23)
<console>:16: toString is not a valid implicit value for Int(23) => String because:
no arguments allowed for nullary method toString: ()String
       foo(23)
           ^

所以看起来隐含在另一个toString 上消失了?老实说,我不知道这里发生了什么,但我有 90% 的把握这是一个错误。

【讨论】:

    【解决方案2】:

    不确定这是否算作一个答案(可能对编译器内部有更多了解的人可以给出更详细的解释),但是在玩了一段时间你的代码后,我发现了一些东西,我认为这是错误的根源。

    给定:

    object Foo {
      implicit def toString(i: Int): String = i.toString      
    }
    
    import Foo.toString
    

    然后:

    val s: String = 10
    

    生产:

    :10: 警告:导入的“toString”被 Object 类中的 toString 方法定义永久隐藏
    导入 Foo.toString

    我认为这意味着,隐式转换被隐藏了,因为它的名称与java.langObject 中定义的通用toString 方法相冲突(和scala.Any


    够古玩了,这行得通。

    implicit val int2str: Int => String = Foo.toString
    val s: String = 10
    // s: String = 10
    

    【讨论】:

    • 但是您更改了原始代码...如果您正在导入 import Foo.toString,则该错误是有意义的。可能相关且方向正确,但不是实际答案 - 我的 toString 重载是完全合法的(不同数量的参数)
    • @pathikrit 正如我所说,我知道这不是答案,但想分享我的发现(如果可能,我会发表评论)。但是,我认为导入的更改不会影响,因为无法选择要导入的两种toString 方法中的哪一种。此外,Travis 的评论表明问题可能与方法的重载没有直接关系,而是 (可能)toString 方法相关的编译器内部的东西有关。
    • 我尝试了其他编译​​器专用的名称(例如equals),但在其他名称(例如hashcode)编译时无法编译。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-16
    • 1970-01-01
    相关资源
    最近更新 更多