0

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

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
Revje
  • 66
  • 1
  • 6
  • 2
    That implicit is simply not in the scope you want. Check the [FAQ](https://docs.scala-lang.org/tutorials/FAQ/finding-implicits.html) 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 Nov 02 '20 at 18:33
  • Sorry but I still don't get why the `SimpleObject` is not in scope, and how to bring in scope – Revje Nov 02 '20 at 18:49
  • 2
    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 worked – Luis Miguel Mejía Suárez Nov 02 '20 at 18:53
  • 3
    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: https://contributors.scala-lang.org/t/can-we-wean-scala-off-implicit-conversions/4388 – Rodrigo Vedovato Nov 02 '20 at 21:15
  • 2
    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 it – Rodrigo Vedovato Nov 02 '20 at 21:16
  • @RodrigoVedovato Yes, I had the suspect that you have to explicitly import the implicit methods so to let the compiler know you are sure of what you are doing. Should I use method overload then? – Revje Nov 02 '20 at 23:20

1 Answers1

3

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

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
  • Thanks a lot. I'm still a beginner so I didn't peek too much in technical details. Your answer is easily understandable – Revje Nov 03 '20 at 09:47