簡體   English   中英

Scala 從隱式 class 方法中選擇了錯誤的隱式轉換

[英]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)

}

版本:

  • Scala 2.12.10
  • SBT 1.4.3
  • JDK 1.8.0_241

隱式解析在編譯時“分派”,因此它只能訪問特定位置的編譯器可用的(類型)信息。

這里

  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 的參數是某個TArray[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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM