1
votes

I'm reading the docs on implicits in Scala, and there is an example of a function with implicit conversion as parameter:

def getIndex[T, CC](seq: CC, value: T)(implicit conv: CC => Seq[T]) = seq.indexOf(value)

I understand how it works, but I don't understand what's the point of writing it like that instead of:

def getIndexExplicit[T](seq: Seq[T], value: T) = seq.indexOf(value)

As far as I know, if the conversion from the argument seq to type Seq[T] exists, the compiler would still allow the call to getIndexExplicit?

To illustrate my point, I prepared this simple example:

def equal42[T](a: T)(implicit conv: T => Int) = conv(a) == 42  // implicit parameter version
def equal42Explicit(a: Int) = a == 42                          // just use the type in the signature

implicit def strToInt(str: String): Int = java.lang.Integer.parseInt(str) // define the implicit conversion from String to Int

And indeed, both functions seem to work in the same way:

scala> equal42("42")
res12: Boolean = true

scala> equal42Explicit("42")
res13: Boolean = true

If there is no difference, what's the point of explicitly defining the implicit conversion?

My guess is that in this simple case it makes no difference, but there must be some more complex scenarios where it does. What are those?

1
Sure your example doesn't make sense, because your function already expects an Int Try equal42Explicit[T](t: T): Boolean = t == 42 - The idea is simple, you get the conversion, so you can use it explicitly or let it be passed implicitly down the stack, or even do not use it at all. Also, the first one allows the caller to pass it explicitly if you want.Luis Miguel Mejía Suárez
Then equal42Explicit("42") returns false! Why is that?siledh
It gets passed as a String and a String is always different to an Int. That is a problem with universal equality that Java uses and thus Scala.Luis Miguel Mejía Suárez
Ah, of course. Thanks!siledh

1 Answers

3
votes

In your super-simple example:

equal42("42")
equal42Explicit("42")

is equal to

equal42("42")(strToInt)
equal42Explicit(strToInt("42"))

which in case of your definition make no difference.

BUT if it did something else e.g.

def parseCombined[S, T](s1: S, s2: S)
                       (combine: (S, S) => S)
                       (implicit parse: S => Option[T]): Option[T] =
  parse(combine(s1, s2))

then when you would apply conversion matters:

implicit def lift[T]: T => Option[T] = t => Option(t)
implicit val parseInt: String => Option[Int] = s => scala.util.Try(s.toInt).toOption
implicit def parseToString[T]: T => Option[String] = t => Option(t.toString)
parseCombined[Option[Int], String]("1", "2") { (a, b) => a.zip(b).map { case (x, y) => x + y } } // Some("Some(3)")
parseCombined[String, Int]("1", "2") { _ + _ } //  Some(12)

In the first case arguments were converted before passing (and then again inside), while in the other case they were converted only inside a function at a specific place.

While this case is somewhat stretched, it shows that having control over when conversion is made might matter to the final result.

That being said such usage of implicit conversions is an antipattern and type classes would work much better for it. Actually extension methods are the only non-controversial usages of implicit conversions because even magnet pattern - the only other use case that might be used on production (see Akka) - might be seen as issue.

So treat this example from docs as a demonstration of a mechanism and not as a example of good practice that should be used on production.