¿Cómo puedo encadenar implicitos en Scala?

El patrón chulo-mi-biblioteca me permite aparentemente agregar un método a una clase haciendo disponible una conversión implícita de esa clase a una que implementa el método.

Sin embargo, Scala no permite que se lleven a cabo dos conversiones implícitas de este tipo, así que no puedo pasar de A a C usando un A a B implícito y otro B a C implícito. ¿Hay alguna forma de evitar esta restricción?

    Scala tiene una restricción en las conversiones automáticas para agregar un método, que es que no aplicará más de una conversión al intentar encontrar métodos. Por ejemplo:

     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: Ver límites (‘< %') están en desuso desde Scala 2.11 https://issues.scala-lang.org/browse/SI-7629 (Puede usar clases de tipo en su lugar)

    Sin embargo, si una definición implícita requiere un parámetro implícito en sí mismo (View bound), Scala buscará valores implícitos adicionales durante el tiempo que sea necesario. Continuar desde el último ejemplo:

     // 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!", Podrías decir. No tan. Aquí es cómo el comstackdor traduciría cada uno:

     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) } 

    Entonces, mientras bToC se usa como una conversión implícita, aToB y aToB se pasan como parámetros implícitos , en lugar de estar encadenados como conversiones implícitas.

    EDITAR

    Pregunta relacionada de interés:

    • Una discusión sobre tipos, origen y precedencia de implicidades

    Tenga en cuenta que también puede crear círculos con parámetros implícitos. Esos, sin embargo, son detectados por el comstackdor, como se muestra en este documento:

     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 } 

    Sin embargo, los errores dados al usuario no son tan claros como podrían ser; simplemente se queja de could not find implicit value for parameter para los tres sitios de construcción. Eso podría oscurecer el problema subyacente en casos menos evidentes.

    Aquí hay un código que también acumula la ruta.

     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))