【问题标题】:Scala selects wrong implicit conversion from within implicit class methodScala 从隐式类方法中选择了错误的隐式转换
【发布时间】:2021-04-15 08:54:59
【问题描述】:

当转换发生在隐式类声明中时,编译器无法选择正确的隐式转换方法。在下面的示例中,我有一个 Foo[T] 类和一个隐式 Helper 类,它采用 Foo 并提供 print 方法。该打印方法调用show,它本身就是Foo 上的隐式转换提供的方法。

问题是有两种可能的转换提供show:一种将Foo[T] 转换为Bar[T],另一种将Foo[Array[T]] 转换为BarArray[T]。这个想法是,当我们有一个包含数组的Foo 时,我们希望应用更具体的BarArray 转换。据我了解,编译器首先选择最具体类型的转换。

这在正常上下文中有效,如下例所示,但在隐式 Helper 类中的 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

【问题讨论】:

  • 您需要为Foo[Array[T]] 提供第二个助手,但是我建议您停止使用如此多的隐式转换,而是寻找基于 typeclass 的解决方案。

标签: scala generics implicit-conversion implicit implicit-class


【解决方案1】:

隐式解析在编译时“分派”,因此它只能访问特定位置的编译器可用的(类型)信息。

这里

  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]现在必须解析相同的代码,没有隐式作为参数传入,并且必须编译一次解决方案,然后键入擦除踢以留下最适合此处的任何隐式的硬编码值。 fooToBar 完美运行。 fooArrayToBarArray 期望证明 Foo 的参数是 Array[T] 对于某些 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 外部与其内部之间没有发生此类通信,因此那里解决的任何问题都适用于传递的所有内容。

【讨论】:

  • 已修复,我知道这些限制,但在凌晨 2 点我认为速度较慢。
  • 谢谢!现在这对我来说很有意义。我会说,试图将隐式转换作为隐式参数传递给隐式类中的方法进行推理需要几次尝试才能理解。我认为我们以这些不同的方式重复使用隐含的单词和关键字的方式会引起混淆。
  • 大家都同意。这就是为什么在 Scala 3 中implicit 仍然有效,但不赞成使用类型类(givenusing 关键字)、扩展方法(extension 关键字)和转换(扩展scala.Conversion)的单独构造。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-11-18
  • 1970-01-01
  • 1970-01-01
  • 2014-02-12
  • 2014-04-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多