2

Yesterday I hit a problem with an implicit conversion. Putting this into import scope:

object FooConversions { implicit def toString(foo: Foo): String = foo.toString }

gave me compile errors. Took a while to figure out that calling the function toString was the culprit. I didn't spend to much time investigating further but my guess is that it is clashing with the toString method in Object? How does the compiler handle an implicit conversion like this?

scala> case class Foo(name: String)
defined class Foo

scala> object FooConversions {
     |   implicit def toString(foo: Foo): String = foo.toString
     | }

defined object FooConversions

scala> import FooConversions._
import FooConversions._

scala> val x: String = Foo("bob")
<console>:16: error: type mismatch;
 found   : Foo
 required: String
       val x: String = Foo("bob")
reikje
  • 2,850
  • 2
  • 24
  • 44
  • 2
    This question has a good answer on where scala looks for implicits http://stackoverflow.com/questions/5598085/where-does-scala-look-for-implicits – colinjwebb Jan 05 '16 at 09:03
  • 1
    You can ofcourse fix this by giving the method in `FooConversions` another name, for example `fooToString`. – Jesper Jan 05 '16 at 09:04

1 Answers1

2

This happens due to member shadowing. The imported toString is not available in the scope where you expect the implicit conversion to happen. Let's see how it works...

First let's check that implicit works:

scala> object FooConversions {
     |   implicit def toString(foo: Foo): String = foo.toString
     |
     |   val x: String = Foo("bob")
     | }
defined object FooConversions

Yes it does in the original/definition scope.

Now let's try to see if the imported method toString is available:

object FooConversions {
  implicit def toString(foo: Foo): String = foo.toString
}

import FooConversions._

val y: String = toString(Foo("bob"))
<console>:28: error: too many arguments for method toString: ()String
       val y: String = toString(Foo("bob"))

It's not available - it's being shadowed by the toString of the enclosing object. In REPL for the code to compile Scala puts everything in some magic object. In real life code will reside in some object/class.

Trying to call toString explicitly to check if it could potentially work:

scala> val y: String = FooConversions.toString(Foo("bob"))
y: String = Foo(bob)

No problem here obviously.

So the solution is either (1) to rename toString to some other name that can't be potentially shadowed:

scala> import scala.language.implicitConversions
import scala.language.implicitConversions

scala> case class Foo(name: String)
defined class Foo

scala> object FooConversions {
     |   implicit def toStr(foo: Foo): String = foo.toString
     | }
defined object FooConversions

scala> import FooConversions._
import FooConversions._

scala> val x: String = Foo("bob")
x: String = Foo(bob)

or (2) to redefine an implicit (sucks):

scala> import scala.language.implicitConversions
import scala.language.implicitConversions

scala> case class Foo(name: String)
defined class Foo

scala> object FooConversions {
     |   implicit def toString(foo: Foo): String = foo.toString
     | }
defined object FooConversions

scala> implicit def toStr(foo: Foo) = FooConversions.toString(foo)
toStr: (foo: Foo)String

scala> val x: String = Foo("bob")
x: String = Foo(bob)

p.s. This is not an issue of implicit lookup or implicit scope, but an issue of shadowing/hiding of imported members.

yǝsʞǝla
  • 16,272
  • 2
  • 44
  • 65
  • Great answer, do you know why it is shadowed? The original `toString()` method doesn't take any parameters, so shouldn't this count as overloading? – reikje Jan 05 '16 at 12:43
  • Glad I could help. Overloading happens when you define a member either via `def` or via inheritance and in that case there would be no conflict as in `object FooConversions { implicit def toString }` - has 2 `toString` methods. When we import members we reference them from outer scope, and inner scope always "wins" by hiding the outer scope names. I think Scala first does the name resolution removing our `toString` and only then resolves implicits thus not being able to find that implicit conversion. – yǝsʞǝla Jan 05 '16 at 13:43