[英]Scala selects wrong implicit conversion from within implicit class method
當轉換發生在隱式 class 聲明中時,編譯器無法選擇正確的隱式轉換方法。 在下面的示例中,我有一個Foo[T]
class 和一個隱式Helper
class ,它采用Foo
並提供print
方法。 該 print 方法調用show
,它本身是Foo
的隱式轉換提供的方法。
問題是有兩種可能的轉換提供show
:一種將Foo[T]
轉換為Bar[T]
,另一種將Foo[Array[T]]
轉換為BarArray[T]
。 這個想法是,當我們有一個包含數組的Foo
時,我們希望應用更具體的BarArray
轉換。 據我了解,編譯器首先選擇具有最具體類型的轉換。
這在正常上下文中有效,如下例所示,但在隱式Helper
class 中的print
方法的上下文中中斷。 在那里,調用了相同的show
方法,因此我希望應該應用相同的轉換。 但是,在這種情況下,編譯器總是選擇Bar
轉換,即使它有Foo[Array[T]]
並且應該選擇BarArray
轉換。
出了什么問題?
最小的失敗代碼示例:
package scratch
import scala.language.implicitConversions
class Foo[T](val value: T) {}
object Foo {
implicit def fooToBar[T](foo: Foo[T]): Bar[T] = {
new Bar(foo.value)
}
implicit def fooArrayToBarArray[T](foo: Foo[Array[T]]): BarArray[T] = {
new BarArray(foo.value)
}
}
class Bar[T](val value: T) {
def show(): String = {
s"Bar($value)"
}
}
class BarArray[T](val value: Array[T]) {
def show(): String = {
value.map(v => s"Bar($v)").mkString(", ")
}
}
object Scratch extends App {
implicit class Helper[T](foo: Foo[T]) {
def print(): Unit = {
println(foo.show())
}
}
val foo0 = new Foo(123)
val foo1 = new Foo(Array(123, 456))
// conversions to Bar and BarArray work correctly here
println(foo0.show()) // Bar(123)
println(foo1.show()) // Bar(123), Bar(456)
// conversions called from within the implicit Helper class
// always choose the Bar conversion
foo0.print // Bar(123)
foo1.print // Bar([I@xxxxxxxx) <- should be Bar(123), Bar(456)
}
版本:
隱式解析在編譯時“分派”,因此它只能訪問特定位置的編譯器可用的(類型)信息。
這里
val foo0 = new Foo(123)
val foo1 = new Foo(Array(123, 456))
// conversions to Bar and BarArray work correctly here
println(foo0.show()) // Bar(123)
println(foo1.show()) // Bar(123), Bar(456)
編譯器以這種方式推斷類型和隱含:
val foo0: Foo[Int] = new Foo(123)
val foo1: Foo[Array[Int]] = new Foo(Array(123, 456))
println(fooToBar(foo0).show()) // Bar(123)
// fooArrayToBarArray instead fooToBar because
// compiler knows that foo1: Foo[Array[Int]]
println(fooArrayToBarArray(foo1).show()) // Bar(123), Bar(456)
然而在這里:
implicit class Helper[T](foo: Foo[T]) {
def print(): Unit = {
println(foo.show())
}
}
所有編譯器都知道 foo: Foo[T]
。 現在必須解析相同的代碼,沒有像 arguments 一樣的隱式傳入,並且必須編譯一次解決方案,然后鍵入擦除鍵,留下最適合此處的任何隱式的硬編碼值。 fooToBar
完美運行。 fooArrayToBarArray
期望證明 Foo 的參數是某個T
的Array[T]
,但無處可尋。 通過在此處傳遞數組,您會忘記它,從而使編譯器無法使用特定於數組的實現。
這就是@LuisMiguelMejíaSuárez 建議類型類的原因:
// type class
trait FooPrinter[A] {
def show[A](foo: Foo[A]): String
def print[A](foo: Foo[A]): Unit = println(show(foo))
}
object FooPrinter {
// convenient summon method
def apply[A](implicit printer: FooPrinter[A]): FooPrinter[A] = printer
}
class Foo[T](val value: T)
// making sure that arrayPrinter takes precedence over tPrinter
// if both match requirements
object Foo extends FooLowPriorityImplicits {
implicit def arrayPrinter[T]: FooPrinter[Array[T]] =
_.map(v => s"Bar($v)").mkString(", ")
}
trait FooLowPriorityImplicits {
implicit def tPrinter[T]: FooPrinter[T] = v => s"Bar($v)"
}
implicit class Helper[T](private val foo: Foo[T]) extends AnyVal {
// requiring type class and summoning it using summon method
def print(implicit fp: FooPrinter[T]): Unit = FooPrinter[T].print(foo)
}
val foo0 = new Foo(123)
val foo1 = new Foo(Array(123, 456))
foo0.print
foo1.print
這樣, Helper
就不必選擇一個隱式並“硬編碼”它,因為它將作為參數傳遞給 at:
new Helper(foo0).print(tPrinter)
new Helper(foo1).print(arrayPrinter)
雖然對我們來說很方便,但它將由編譯器完成。 在您的示例中, Helper
外部與其內部之間沒有發生此類通信,因此在那里解決的任何問題都將應用於傳遞的所有內容。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.