Bagaimana saya bisa merangkai implisit di Scala?

Pola pimp-my-library memungkinkan saya menambahkan metode ke kelas dengan menyediakan konversi implisit dari kelas tersebut ke kelas yang mengimplementasikan metode tersebut.

Namun, Scala tidak mengizinkan dua konversi implisit seperti itu terjadi, jadi saya tidak dapat berpindah dari A ke C menggunakan implisit A ke B dan implisit lainnya B ke C. Apakah ada jalan keluar dari pembatasan ini?


person Daniel C. Sobral    schedule 16.03.2011    source sumber
comment
@ryeguy Ini pertanyaan meta untuk debat germo/memperkaya, karena tag itu sangat jelek. Tag itu...   -  person Charles    schedule 15.06.2013


Jawaban (3)


Scala memiliki batasan pada konversi otomatis untuk menambahkan metode, yaitu tidak akan menerapkan lebih dari satu konversi dalam mencoba menemukan metode. Misalnya:

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

EDIT: Batas tampilan ('‹%') tidak digunakan lagi sejak Scala 2.11 https://issues.scala-lang.org/browse/SI-7629 (Anda bisa menggunakan kelas tipe sebagai gantinya)

Namun, jika definisi implisit memerlukan parameter implisit itu sendiri (batas tampilan), Scala akan mencari nilai implisit tambahan selama diperlukan. Lanjutkan dari contoh terakhir:

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

"Ajaib!", Anda mungkin berkata. Tidak begitu. Berikut adalah cara kompiler menerjemahkan masing-masingnya:

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

Jadi, ketika bToC digunakan sebagai konversi implisit, aToB dan toA diteruskan sebagai parameter implisit, bukannya dirangkai sebagai konversi implisit.

EDIT

Pertanyaan terkait yang menarik:

person Daniel C. Sobral    schedule 16.03.2011
comment
Penjelasan yang bagus. Alasan lain untuk melarang rangkaian konversi implisit adalah untuk menghindari kerumitan dan mimpi buruk proses debug. Saya bertanya-tanya mengapa rangkaian diizinkan untuk parameter implisit? - person Adrian; 17.03.2011
comment
Bagus! Saya belajar sesuatu yang baru. Ini harusnya ada di halaman fitur tersembunyi. - person Aaron Novstrup; 17.03.2011
comment
Terima kasih! Satu masalah kecil: Saya harus menambahkan jenis hasil eksplisit ke fungsi aToB dan bToC di T2 ketika saya mencobanya di REPL. - person Agl; 04.04.2011
comment
@Agl Ini tidak diperlukan pada versi 2.9 mendatang, tetapi saya telah memodifikasi kodenya agar kompatibel dengan 2.8. Terima kasih. - person Daniel C. Sobral; 04.04.2011
comment
Sekadar catatan bahwa rangkaian yang Anda coba lakukan melibatkan tipe dengan jenis yang lebih tinggi, maka inferensi tipe dapat membuat Anda kesal. Yaitu. Saya punya M[A]. Saya memiliki implisit A=›B dan implisit M[] =› N[], dengan M dan N bersifat monadik. Saya ingin membuat N[B] menggunakan dua konversi. Merangkai ini memerlukan pemanggilan metode tambahan, yang pertama untuk menangkap M[_] dan yang kedua untuk menangkap A. - person jsuereth; 11.04.2011
comment
@jsuereth Ya, itu bisa jadi rumit. Saya akan melihat apakah saya dapat memberikan contoh yang masuk akal, singkat dan dapat dimengerti. - person Daniel C. Sobral; 11.04.2011
comment
@Peter Lebih baik mengajukan pertanyaan dengan masalah khusus itu. Saya pikir menambahkannya di sini akan melenceng dari topik. - person Daniel C. Sobral; 17.09.2011
comment
Terima kasih @DanielC.Sobral! Jawaban yang hebat, sangat memperjelas banyak hal bagi saya. - person Tomer Gabel; 18.11.2013
comment
@ DanielC.Sobral Itu jawaban yang bagus! Setelah membacanya beberapa kali, tidak terlihat seperti voodoo gila lagi. - person mo-seph; 18.12.2013
comment
Tanpa batas tampilan: docs.scala-lang.org/tutorials/FAQ/ chaining-implicits.html (lihat komentar di bawah) - person eirirlar; 27.01.2016

Perhatikan bahwa Anda juga dapat membuat lingkaran dengan parameter implisit. Namun, hal tersebut terdeteksi oleh kompiler, seperti yang ditunjukkan oleh ini:

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
}

Namun, kesalahan yang diberikan kepada pengguna tidak sejelas mungkin; itu hanya mengeluh could not find implicit value for parameter untuk ketiga lokasi konstruksi. Hal ini mungkin mengaburkan masalah mendasar dalam kasus-kasus yang kurang jelas.

person Raphael    schedule 17.03.2011

Berikut kode yang juga mengakumulasikan jalurnya.

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