Як я можу зв’язати наслідки в Scala?


77

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

Однак Scala не дозволяє проводити два таких неявних перетворення, тому я не можу Aперейти від Cвикористання неявного Aдо Bі іншого неявного Bдо C. Чи є спосіб обійти це обмеження?


4
@ryeguy Ось мета-запитання для суперечки / збагачення , бо святе лайно це тег. Ця мітка ...
Чарльз,

Відповіді:


106

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передається як неявні параметри , замість того, щоб бути прив’язаним як неявні перетворення.

РЕДАГУВАТИ

Пов’язане питання, що цікавить:


5
Гарне пояснення. Зазначена причина заборони неявного ланцюжка перетворень полягає в уникненні складності та налагодження кошмару. Цікаво, чому тоді дозволяється ланцюжок для неявних параметрів?
Адріан,

3
Приємно! Я дізнався щось нове. Це має бути на сторінці "приховані функції".
Аарон Новструп,

Дякую! Одна невелика проблема: я повинен був додати додати явні типи результатів до функцій aToBі bToCв T2коли я спробував його в REPL.
Agl,

@Agl Це не буде потрібно в майбутньому 2.9, але я змінив код, щоб зробити його сумісним з 2.8. Дякую.
Daniel C. Sobral

1
Тільки примітка, що це ланцюжок, яку ви намагаєтесь зробити, включає вищі типи, тоді висновок типу знову може вас завадити. Тобто у мене є M [A]. У мене є неявний A => B і неявний M [ ] => N [ ], де і M, і N є монадичними. Я хочу створити N [B] за допомогою двох перетворень. Для зв’язування цих ланцюгів потрібен додатковий виклик методу, перший для захоплення M [_], а другий для захоплення А.
jsuereth

12

Зверніть увагу, що ви також можете створювати кола з неявними параметрами. Однак їх виявляє компілятор, як показано в цьому:

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на всі три будівельні майданчики. Це може затемнити основну проблему в менш очевидних випадках.


1

Ось код, який також накопичує шлях.

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))
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.