Como posso encadear implícitos no Scala?

O padrão pimp-my-library permite que eu adicione um método a uma class, disponibilizando uma conversão implícita dessa class para uma que implemente o método.

No entanto, o Scala não permite duas conversões implícitas desse tipo, portanto, não consigo ir de A para C usando um A implícito para B e outro implícito B para C Existe alguma maneira de contornar essa restrição?

O Scala tem uma restrição nas conversões automáticas para adicionar um método, ou seja, não aplicará mais de uma conversão ao tentar encontrar methods. Por exemplo:

 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(an, an) implicit def bToC(b: B): C = new C(bm, bn, bm + bn) // 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) } 

EDITAR: Exibir limites (‘< %') estão obsoletos desde Scala 2.11 https://issues.scala-lang.org/browse/SI-7629 (você pode usar classs de tipo em vez disso)

No entanto, se uma definição implícita exigir um próprio parâmetro implícito (View bound), o Scala procurará valores implícitos adicionais pelo tempo que for necessário. Continue no último exemplo:

 // 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(an, an) implicit def bToC[B1 <% B](b: B1): C = new C(bm, bn, bm + bn) // works println(5.total) println(new A(5).total) println(new B(5, 5).total) println(new C(5, 5, 10).total) } 

"Magia!", Você poderia dizer. Não tão. Aqui está como o compilador traduziria cada um:

 object T1Translated { implicit def toA(n: Int): A = new A(n) implicit def aToB(a: A): B = new B(an, an) implicit def bToC(b: B): C = new C(bm, bn, bm + bn) // 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(an, an) implicit def bToC[B1 <% B](b: B1): C = new C(bm, bn, bm + bn) // 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) } 

Assim, enquanto o bToC está sendo usado como uma conversão implícita, aToB e aToB estão sendo passados ​​como parâmetros implícitos , em vez de serem encadeados como conversões implícitas.

EDITAR

Questão relacionada de interesse:

  • Uma discussão sobre tipos, origem e precedência de implícitos

Observe que você também pode criar círculos com parâmetros implícitos. Esses são, no entanto, detectados pelo compilador, como exibido por isso:

 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 } 

O (s) erro (s) fornecido (s) ao usuário não está tão claro quanto possível; apenas reclama could not find implicit value for parameter para todos os três canteiros de obras. Isso pode obscurecer o problema subjacente em casos menos óbvios.

Aqui está um código que também acumula o caminho.

 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(xl :+ 'A') implicit def bc[B1 <% B](x: B1) = C(xl :+ 'B') implicit def ce[C1 <% C](x: C1) = E(xl :+ 'C') implicit def ea[E1 <% E](x: E1) = A(xl :+ 'E') def pathFrom(end:D) = end pathFrom(B(Nil)) // res0: D = D(List(B, C, E, A))