Как я могу связать имплициты в Scala?

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

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


person Daniel C. Sobral    schedule 16.03.2011    source источник
comment
@ryeguy Вот мета-вопрос для дискуссии о сутенерстве / обогащении, потому что, черт возьми, этот тег. Этот тег ...   -  person Charles    schedule 15.06.2013


Ответы (3)


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 передаются как неявные параметры, а не связаны как неявные преобразования.

ИЗМЕНИТЬ

Связанный интересующий вопрос:

person Daniel C. Sobral    schedule 16.03.2011
comment
Хорошее объяснение. Заявленная причина запрета неявного преобразования в цепочку состоит в том, чтобы избежать сложности и кошмара отладки. Интересно, почему тогда для неявных параметров разрешена цепочка? - person Adrian; 17.03.2011
comment
Отлично! Я узнал кое-что новое. Это должно быть на странице скрытых функций. - person Aaron Novstrup; 17.03.2011
comment
Спасибо! Одна небольшая проблема: мне пришлось добавить явные типы результатов к функциям aToB и bToC в T2, когда я попробовал это в REPL. - person Agl; 04.04.2011
comment
@Agl В следующем выпуске 2.9 необходимости нет, но я изменил код, чтобы он был совместим с версией 2.8. Спасибо. - person Daniel C. Sobral; 04.04.2011
comment
Просто обратите внимание, что цепочка, которую вы пытаетесь сделать, включает в себя типы более высокого порядка, и опять же, вывод типа может вас утомить. Т.е. У меня M [A]. У меня есть неявное A = ›B и неявное M [] =› N [], где M и N монадические. Я хочу создать N [B], используя два преобразования. Для их объединения требуется дополнительный вызов метода, первый для захвата M [_], а второй для захвата A. - person jsuereth; 11.04.2011
comment
@jsuereth Да, это может быть сложно. Я посмотрю, смогу ли я разработать разумный короткий и понятный пример. - person Daniel C. Sobral; 11.04.2011
comment
@Peter Лучше задать вопрос по этой конкретной проблеме. Я думаю, что добавление этого здесь будет отклонением от темы. - person Daniel C. Sobral; 17.09.2011
comment
Спасибо @ DanielC.Sobral! Потрясающий ответ, действительно многое прояснило для меня. - person Tomer Gabel; 18.11.2013
comment
@ DanielC.Sobral Это отличный ответ! Прочитав его несколько раз, он больше не похож на сумасшедшее вуду. - person mo-seph; 18.12.2013
comment
Без границ просмотра: docs.scala-lang.org/tutorials/FAQ/ chaining-implits.html (см. комментарий внизу) - person eirirlar; 27.01.2016

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

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 на все три стройки. В менее очевидных случаях это может скрыть основную проблему.

person Raphael    schedule 17.03.2011

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

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))
person Sagie Davidovich    schedule 11.03.2013