如果def名称为toString,则Scala隐式def不起作用

时间:2019-02-25 17:54:08

标签: scala implicit

此代码无法编译:

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等,则它可以工作。

2 个答案:

答案 0 :(得分:12)

这里的问题不是toString上的Foo重载,正如其他答案(现已删除)之一指出的那样(您可以尝试类似地重载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))
}

这将使用asString中的Bar,即使您可能会认为导入的优先级:

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) + ""
                     ^

我们所做的是定义一个隐式值,该值在any2stringadd中隐藏了scala.Predef隐式转换。 (是的,这很恐怖。)

脚注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%的人肯定这是一个错误。

答案 1 :(得分:2)

不确定这是否可以作为答案(可能是对编译器内部知识有更多了解的人可以提供更详细的解释),但是在玩了一段时间之后,我发现了一些东西,我相信这是错误的根源。

给出:

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

import Foo.toString

然后:

val s: String = 10

产生:

  

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

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


好奇,这行得通。

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