wohanley
wohanley

Reputation: 434

Implicit conversion works for symbol but not string

I'm working on a DSL for some formal grammar based stuff. I'd like to be able to say something like 'start produces "a" andThen 'b andThen "c", where symbols and strings represent different components of the grammar. I'm seeing a problem with code like this:

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
}

It seems my implicit conversion works fine for a symbol, but when I try to implicitly convert a string to a Wrapper, I get a compiler error: "value andThen is not a member of String". Why?

Upvotes: 3

Views: 299

Answers (1)

Ben Reich
Ben Reich

Reputation: 16324

The compiler is getting confused because of the andThen method that is defined on Function. Here is a minimal example:

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

  "foo" andThen "bar"
}

This fails to compile with the same error as your example. Try renaming andThen to anything else (e.g. andThen2) and see that this compiles in order to convince yourself that this is the problem.

Here's what's going on. The compiler knows how to convert String to Int => Char through existing implicits:

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

Since Function already has an andThen method, this is tripping up the compiler. Of course, a perfect compiler could feasibly choose the correct conversion here, and maybe it should according to the spec (I haven't looked into it too carefully). However, you can also just help it along with a hint. In your example, you might try:

object StringGrammar extends ImplicitTest {
  ("x" : Wrapper) andThen "y"
}

You could also just use a different method name.

Another way to verify that this is the error is to exclude the implicit wrapString, which converts String to WrappedString, which implements PartialFunction and thereby exposes the problematic andThen method that is causing the conflict:

//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"
}

Note that this technique doesn't work in the REPL: Predef un-importing has to be in a file and the first import. But the above code compiles with scalac, for example.

When implicits aren't working as expected, it can sometimes be useful to look at the implicits in Predef for conflicts.

Upvotes: 6

Related Questions