Implicit conversion, import required or not?

2019-04-04 08:23发布

问题:

I write

object MyString {
  implicit def stringToMyString(s: String) = new MyString(s)    
}

class MyString(str: String) {
  def camelize = str.split("_").map(_.capitalize).mkString

  override def toString = str
}


object Parse {
  def main(args: Array[String]) {
    val x = "active_record".camelize
    // ...
  }
}

in my program. This causes a compiling error. After I inserted

  import MyString.stringToMyString

Then it works.

From Odersky's Programming in Scala I got that implicit conversion in the companion object of the source or expected target types don't need to be imported.

回答1:

implicit conversion in the companion object of the source or expected target types don't need to be imported.

True enough. Now, the method camelize is defined on the class MyString, and, indeed, there is an implicit conversion to MyString inside its object companion. However, there is nothing in the code telling the compiler that MyString is the expected target type.

If, instead, you wrote this:

val x = ("active_record": MyString).camelize

then it would work, because the compiler would know you expect "active_record" to be a MyString, making it look up the implicit conversion inside object MyString.

This might look a bit restrictive, but it actually works in a number of places. Say, for instance, you had:

class Fraction(num: Int, denom: Int) {
    ...
    def +(b: Fraction) = ...
    ...
}

And then you had a code like this:

val x: Fraction = ...
val y = x + 5

Now, x does have a + method, whose expected type is Fraction. So the compiler would look, here, for an implicit conversion from Int to Fraction inside the object Fraction (and inside the object Int, if there was one, since that's the source type).



回答2:

In this situation you need the import because the compiler doesn't know where you pulled out the camelize method from. If the type is clear, it will compile without import:

object Parse {
  def foo(s: MyString) = s.camelize

  def main(args: Array[String]) {
    val x = foo("active_record")
    println(x.toString)
  }
}

See Pimp my library pattern, based on Martin's article:

Note that it is not possible to put defs at the top level, so you can’t define an implicit conversion with global scope. The solution is to place the def inside an object, and then import it, i.e.

object Implicits {
    implicit def listExtensions[A](xs : List[A]) = new ListExtensions(xs)
}

And then at the top of each source file, along with your other imports:

import Implicits._


回答3:

I tried the Rational class example in Programming in Scala book, put an implicit method in its companion object:

object Rational {
  implicit def intToRational(num: Int) = 
    new Rational(num)
}

but the code

2 + new Rational(1, 2)

does not work. For the conversion to happen, the single identifier rule applies, i.e., you need to import the explicit method into scope even though it is defined in the companion object.