0
votes

I was playing with implicits but I got some behaviour that I don't understand.

I defined a simple class and its companion object (just to make a quick test on implicits) as follows

class SimpleNumber(val numb: Int) {
    def sum(n: Int) = numb + n
}

object SimpleNumber {
    implicit def stringToInt(s: String) = s.toInt
}

The method stringToInt is supposed to work in the event I call the sum method by passing a string instead of an int, so that it can be converted to an int (it's just for testing purposes, so I don't need to check errors or exceptions).

If I paste the above code in the repl (:paste), I get this warning

warning: implicit conversion method stringToInt should be enabled by making the implicit value scala.language.implicitConversions visible. This can be achieved by adding the import clause 'import scala.language.implicitConversions' or by setting the compiler option -language:implicitConversions.

So I moved to VSCode and pasted in the same code, to see if I could get some more info via metals plugin, but that warning doesn't even pop out. Then I created a new object that extends the App trait to test the code like this

object TestDriver extends App{
    
    val a = new SimpleNumber(4)
    println(a.sum("5"))
}

but I received the following error

type mismatch; found : String("5") required: Int

I tried importing the implicitConversions as the repl suggested, first in the companion object and then in the TestDriver object, but to no avail. Then I imported the implicit method directly in the TestDriver object, and that worked.

import SimpleNumber.stringToInt

object TestDriver extends App{
    val a = new SimpleNumber(4)
    println(a.sum("5"))
}

Why doesn't the import scala.language.implicitConversions work as I thought it would? I'm using scala 2.13.3

1
That implicit is simply not in the scope you want. Check the FAQ when you do a.sum("5") the compiler will try to search for an implicit conversion (which, btw, are discouraged) on the current lexical scope, or in the companion object of the source and target types (Int and String respectively) and neither of those has such implicit. You can, however, import SimpleNumber._ before so the conversion is put into the lexical scope, but that is not what you want. TL;DR; the SimpleNumber object is not in scope.Luis Miguel Mejía Suárez
Sorry but I still don't get why the SimpleObject is not in scope, and how to bring in scopeRevje
I mean that the body of SimpleObject is not in the implicit scope of a transformation of an Int into a String, why? because that is what the spec says (but the rationale is simple, the implicit scope can not be too big because the compiler would be slower). You can bring its implicit members into scope the same way you can bring anything into scope, by explicitly importing it. The rule of companions objects being in the implicit scope is for source and target types, for example, if you rather had a conversion from SimpleObject into String or vice-versa it would have workedLuis Miguel Mejía Suárez
As you seem to be studying Scala, I have to say something unrelated to the question: stay away from implicit conversions. Some words from the man himself: contributors.scala-lang.org/t/…Rodrigo Vedovato
Also, this is why you had to explicitly import scala.language.implicitConversions. In Scala 2.13, it has been disabled by default so you only use it when you're sure you need itRodrigo Vedovato

1 Answers

3
votes

Why doesn't the import scala.language.implicitConversions work as I thought it would?

import scala.language.implicitConversions is just to signal that implicit conversions are defined in a code, not to bring specific ones to a scope. You can do

import SimpleNumber._

to bring stringToInt to the local scope.

Where does Scala look for implicits?

Should I use method overload then?

Depends on your goal. Implicit conversions (which are normally not recommended) are not just for method overloading. In your example String can be used in all places where Int is expected (this is just for example, normally standard types like Int, String, Function shouldn't be used for implicits, use custom types for implicits). If you used implicit conversions just for method overloading then yes, you should prefer the latter. Don't forget that method overloading can be done not only as

class SimpleNumber(val numb: Int) {
  def sum(n: Int):    Int = numb + n
  def sum(n: String): Int = numb + n.toInt
}

but also with a type class

class SimpleNumber(val numb: Int) {
  def sum[A: ToInt](n: A): Int = numb + n.toInt
}

trait ToInt[A] {
  def toInt(a: A): Int
}
object ToInt {
  implicit val int: ToInt[Int]    = identity
  implicit val str: ToInt[String] = _.toInt
}

implicit class ToIntOps[A](val a: A) extends AnyVal {
  def toInt(implicit ti: ToInt[A]): Int = ti.toInt(a)
}

or magnet

import scala.language.implicitConversions
import Predef.{augmentString => _, _} // to avoid implicit ambiguity

class SimpleNumber(val numb: Int) {
  def sum(n: ToInt): Int = numb + n.toInt()
}

trait ToInt {
  def toInt(): Int
}
object ToInt {
  implicit def fromInt(i: Int):    ToInt = () => i
  implicit def fromStr(s: String): ToInt = () => s.toInt
}

Notice that you don't have to import ToInt.

Overloading methods based on generics

Type erasure problem in method overloading