Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I chain implicits in Scala?

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?

like image 507
Daniel C. Sobral Avatar asked Mar 16 '11 22:03

Daniel C. Sobral


People also ask

How do Implicits work in Scala?

The implicit system in Scala allows the compiler to adjust code using a well-defined lookup mechanism. A programmer in Scala can leave out information that the compiler will attempt to infer at compile time. The Scala compiler can infer one of two situations: A method call or constructor with a missing parameter.

Where does Scala look for Implicits?

In this case, it looks inside the object Ordering , companion to the class Ordering , and finds an implicit Ordering[Int] there. This is how Scala found the implicit Numeric[Int] and Numeric[Long] in your question, by the way, as they are found inside Numeric , not Integral .

What are implicit classes in Scala?

An implicit class is a class marked with the implicit keyword. This keyword makes the class's primary constructor available for implicit conversions when the class is in scope. Implicit classes were proposed in SIP-13.


2 Answers

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:

  • A discussion on types, origin and precedence of implicits
like image 60
Daniel C. Sobral Avatar answered Oct 09 '22 11:10

Daniel C. Sobral


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.

like image 22
Raphael Avatar answered Oct 09 '22 11:10

Raphael