隐式转换适用于符号但不适用于字符串

woh*_*ley 2 scala implicit

我正在研究DSL的一些正式的基于语法的东西.我希望能够说出一些代码'start produces "a" andThen 'b andThen "c",其中符号和字符串代表语法的不同组成部分.我看到这样的代码有问题:

class ImplicitTest {

  trait GrammarPart
  case class Nonterminal(name: Symbol) extends GrammarPart
  case class Terminal(value: String) extends GrammarPart


  case class Wrapper(production: Seq[GrammarPart]) {
    def andThen(next: Wrapper) =
      Wrapper(production ++ next.production)
  }

  implicit def symbolToWrapper(symbol: scala.Symbol) =
    Wrapper(Seq(Nonterminal(symbol)))

  implicit def stringToWrapper(s: String) =
    Wrapper(Seq(Terminal(s)))
}

object StringGrammar extends ImplicitTest {
  "x" andThen "y" // this causes a compiler error: "value andThen is not a member of String"
}

object SymbolGrammar extends ImplicitTest {
  'x andThen "y" // this is fine
}
Run Code Online (Sandbox Code Playgroud)

似乎我的隐式转换对于符号工作正常,但是当我尝试将字符串隐式转换为a时Wrapper,我得到编译器错误:"value andThen不是String的成员".为什么?

Ben*_*ich 6

由于andThen定义的方法,编译器变得困惑Function.这是一个最小的例子:

class Foo {
  def andThen(x: Foo) = ???
  implicit def string2foo(s: String): Foo = new Foo

  "foo" andThen "bar"
}
Run Code Online (Sandbox Code Playgroud)

这无法编译与您的示例相同的错误.尝试重命名andThen为其他任何东西(例如andThen2)并看到这个编译以便说服自己这是问题所在.

这是正在发生的事情.编译器知道如何转换StringInt => Char通过现有implicits:

val f: Int => Char = "foobar"
val g = "foobar" andThen { c => s"character is '$c'" }
g(4) //"character is 'b'"
Run Code Online (Sandbox Code Playgroud)

由于Function已经有了一个andThen方法,这会使编译器瘫痪.当然,一个完美的编译器可以在这里切实选择正确的转换,也许它应该根据规范(我没有仔细研究过).但是,您也可以通过提示来帮助它.在您的示例中,您可以尝试:

object StringGrammar extends ImplicitTest {
  ("x" : Wrapper) andThen "y"
}
Run Code Online (Sandbox Code Playgroud)

您也可以使用不同的方法名称.

验证这是错误的另一种方法是排除隐式wrapString转换StringWrappedString,它实现PartialFunction并因此暴露andThen导致冲突的有问题的方法:

//unimport wrapString but import all other Predefs, in order to isolate the problem
import Predef.{wrapString => _, _} 
class Foo {
  def andThen(x: Foo) = ???
  implicit def string2foo(s: String): Foo = new Foo

  "foo" andThen "bar"
}
Run Code Online (Sandbox Code Playgroud)

请注意,此技术在REPL中不起作用:Predef取消导入必须位于文件中并且是第一次导入.但是上面的代码可以编译scalac.

当implicits没有按预期工作时,有时可以查看Predef冲突中的含义.