¿Cómo puedo encadenar implícitos en Scala?

Resuelto Daniel C. Sobral asked hace 13 años • 3 respuestas

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

Sin embargo, Scala no permite que se realicen dos conversiones implícitas, por lo que no puedo pasar Aa Cusar un implicit Ato By otro implicit Bto C. ¿Hay alguna forma de evitar esta restricción?

Daniel C. Sobral avatar Mar 17 '11 05:03 Daniel C. Sobral
Aceptado

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

EDITAR: Los límites de vista ('<%') están en desuso desde Scala 2.11 https://issues.scala-lang.org/browse/SI-7629 (puede usar clases de tipos en su lugar)

Sin embargo, si una definición implícita requiere un parámetro implícito en sí (con vista), Scala buscará valores implícitos adicionales durante el tiempo que sea necesario. Continúe 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(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)
}

"¡Magia!", se podría decir. No tan. Así es como el compilador traduciría cada uno:

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

Por lo tanto, while bToCse utiliza como una conversión implícita aToBy toAse pasa como parámetros implícitos , en lugar de encadenarse como conversiones implícitas.

EDITAR

Pregunta de interés relacionada:

  • Una discusión sobre tipos, origen y precedencia de implícitos.
Daniel C. Sobral avatar Mar 16 '2011 22:03 Daniel C. Sobral

Tenga en cuenta que también puede crear círculos con parámetros implícitos. Sin embargo, el compilador los detecta, como se muestra en esto:

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 proporcionados al usuario no son tan claros como podrían ser; simplemente se queja could not find implicit value for parameterde los tres sitios de construcción. Eso podría oscurecer el problema subyacente en casos menos obvios.

Raphael avatar Mar 17 '2011 13:03 Raphael

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(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 avatar Mar 11 '2013 09:03 Sagie Davidovich