77

The pimp-my-library pattern allows me to seemingly add a method to a class by making available an implicit conversion from that class to one that implements the method.

Scala does not allow two such implicit conversions taking place, however, so I cannot got from A to C using an implicit A to B and another implicit B to C. Is there a way around this restriction?

Ben
  • 48,249
  • 32
  • 114
  • 131
Daniel C. Sobral
  • 284,820
  • 82
  • 479
  • 670
  • 4
    @ryeguy [Here's a meta question for the pimp/enrich debate](http://meta.stackexchange.com/q/184514/135887), because holy crap that tag. *That tag*... – Charles Jun 15 '13 at 05:07

3 Answers3

107

Scala has a restriction on automatic conversions to add a method, which is that it won't apply more than one conversion in trying to find methods. For example:

class A(val n: Int)
class B(val m: Int, val n: Int)
class C(val m: Int, val n: Int, val o: Int) {
  def total = m + n + o
}

// This demonstrates implicit conversion chaining restrictions
object T1 { // to make it easy to test on REPL
  implicit def toA(n: Int): A = new A(n)
  implicit def aToB(a: A): B = new B(a.n, a.n)
  implicit def bToC(b: B): C = new C(b.m, b.n, b.m + b.n)

  // won't work
  println(5.total)
  println(new A(5).total)

  // works
  println(new B(5, 5).total)
  println(new C(5, 5, 10).total)
}

EDIT: View bounds ('<%') are deprecated since Scala 2.11 https://issues.scala-lang.org/browse/SI-7629 (You can use type classes instead)

However, if an implicit definition requires an implicit parameter itself(View bound), Scala will look for additional implicit values for as long as needed. Continue from the last example:

// def m[A <% B](m: A) is the same thing as
// def m[A](m: A)(implicit ev: A => B)

object T2 {
  implicit def toA(n: Int): A = new A(n)
  implicit def aToB[A1 <% A](a: A1): B = new B(a.n, a.n)
  implicit def bToC[B1 <% B](b: B1): C = new C(b.m, b.n, b.m + b.n)

  // works
  println(5.total)
  println(new A(5).total)
  println(new B(5, 5).total)
  println(new C(5, 5, 10).total)
}

"Magic!", you might say. Not so. Here is how the compiler would translate each one:

object T1Translated {
  implicit def toA(n: Int): A = new A(n)
  implicit def aToB(a: A): B = new B(a.n, a.n)
  implicit def bToC(b: B): C = new C(b.m, b.n, b.m + b.n)

  // Scala won't do this
  println(bToC(aToB(toA(5))).total)
  println(bToC(aToB(new A(5))).total)

  // Just this
  println(bToC(new B(5, 5)).total)

  // No implicits required
  println(new C(5, 5, 10).total)
}

object T2Translated {
  implicit def toA(n: Int): A = new A(n)
  implicit def aToB[A1 <% A](a: A1): B = new B(a.n, a.n)
  implicit def bToC[B1 <% B](b: B1): C = new C(b.m, b.n, b.m + b.n)

  // Scala does this
  println(bToC(5)(x => aToB(x)(y => toA(y))).total)
  println(bToC(new A(5))(x => aToB(x)(identity)).total)      
  println(bToC(new B(5, 5))(identity).total)

  // no implicits required
  println(new C(5, 5, 10).total)
}

So, while bToC is being used as an implicit conversion, aToB and toA are being passed as implicit parameters, instead of being chained as implicit conversions.

EDIT

Related question of interest:

Community
  • 1
  • 1
Daniel C. Sobral
  • 284,820
  • 82
  • 479
  • 670
  • 5
    Nice explanation. The stated reason for disallowing implicit conversion chaining is to avoid complexity and debugging nightmare. I wonder why is then chaining allowed for implicit parameters? – Adrian Mar 16 '11 at 23:45
  • 3
    Nice! I learned something new. This should be on the "hidden features" page. – Aaron Novstrup Mar 17 '11 at 17:21
  • Thanks! One minor issue: I had to add add explicit result types to the functions `aToB` and `bToC` in `T2` when I tried it in REPL. – Agl Apr 04 '11 at 08:24
  • @Agl It won't be necessary on the upcoming 2.9, but I have modified the code to make it compatible with 2.8. Thanks. – Daniel C. Sobral Apr 04 '11 at 18:21
  • 1
    Just a note that is the chaining you're trying to do involves higher kinded types, then again type inference can hose you. I.e. I have M[A]. I have an implicit A=>B and an implicit M[_] => N[_], where both M and N are monadic. I want to create an N[B] using the two conversions. Chaining these requires an additional method call, the first to capture M[_] and the second to capture A. – jsuereth Apr 11 '11 at 00:50
  • @jsuereth Yeah, that can be tricky. I'll see if I can work out a reasonable short and understandable example. – Daniel C. Sobral Apr 11 '11 at 01:20
  • @Peter Better to ask a question with that specific problem. I think adding it here would stray off topic. – Daniel C. Sobral Sep 17 '11 at 19:21
  • Thanks @DanielC.Sobral! Terrific answer, really clarified things for me. – Tomer Gabel Nov 18 '13 at 12:44
  • @DanielC.Sobral That's a great answer! After reading it a few times, it doesn't look like crazy voodoo anymore. – mo-seph Dec 17 '13 at 23:49
  • Without view bounds: http://docs.scala-lang.org/tutorials/FAQ/chaining-implicits.html (see comment in the bottom) – eirirlar Jan 27 '16 at 12:34
12

Note that you can build circles with implicit parameters, too. Those are, however, detected by the compiler, as exhibited by this:

class Wrap {
  class A(implicit b : B)
  class B(implicit c : C)
  class C(implicit a : A)

  implicit def c = new C
  implicit def b = new B
  implicit def a = new A
}

The error(s) given to the user are not as clear as they could be, though; it just complains could not find implicit value for parameter for all three construction site. That might obscure the underlying problem in less obvious cases.

Raphael
  • 6,604
  • 2
  • 48
  • 77
1

Here's a code that also accumulates the path.

import scala.language.implicitConversions

// Vertices
case class A(l: List[Char])
case class B(l: List[Char])
case class C(l: List[Char])
case class D(l: List[Char])
case class E(l: List[Char])

// Edges
implicit def ad[A1 <% A](x: A1) = D(x.l :+ 'A')
implicit def bc[B1 <% B](x: B1) = C(x.l :+ 'B')
implicit def ce[C1 <% C](x: C1) = E(x.l :+ 'C')
implicit def ea[E1 <% E](x: E1) = A(x.l :+ 'E')

def pathFrom(end:D) = end

pathFrom(B(Nil))   // res0: D = D(List(B, C, E, A))
Sagie Davidovich
  • 558
  • 4
  • 12