Как я могу подключить implicits в Scala?

Шаблон pimp-my-library позволяет мне, по-видимому, добавить метод к классу, предоставив им неявное преобразование из этого класса в тот, который реализует метод.

Scala не допускает двух таких неявных преобразований, однако я не могу получить от A до C, используя неявный A to B и другой неявный B to C, Есть ли способ ограничить это ограничение?

Ответ 1

Scala имеет ограничение на автоматическое преобразование, чтобы добавить метод, который заключается в том, что он не будет применять более одного преобразования при попытке найти методы. Например:

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

РЕДАКТ.: Оценки просмотра ('<%') устарели, поскольку Scala 2.11 https://issues.scala-lang.org/browse/SI-7629 (Вместо этого вы можете использовать классы типов)

Однако если для неявного определения требуется сам неявный параметр (View bound), Scala будет искать дополнительные неявные значения до тех пор, пока это необходимо. Продолжайте с последнего примера:

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

"Магия!", можно сказать. Не так. Вот как компилятор переведет каждый из них:

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

Итак, в то время как bToC используется как неявное преобразование, aToB и toA передаются как неявные параметры, а не привязаны как неявные преобразования.

ИЗМЕНИТЬ

Интересный вопрос:

Ответ 2

Обратите внимание, что вы также можете создавать круги с неявными параметрами. Тем не менее, они обнаруживаются компилятором, как показано на рисунке:

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
}

Ошибка (ы), предоставленные пользователю, не так понятны, как могли бы быть; он просто жалуется на could not find implicit value for parameter для всех трех строительных площадок. Это может скрыть основную проблему в менее очевидных случаях.

Ответ 3

Вот код, который также накапливает путь.

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