Scala 从隐式 class 方法中选择了错误的隐式转换
Scala selects wrong implicit conversion from within implicit class method
当转换发生在隐式 class 声明中时,编译器无法选择正确的隐式转换方法。在下面的示例中,我有一个 Foo[T]
class 和一个隐式的 Helper
class,它采用 Foo
并提供 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
- JDK1.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]
。必须解决相同的代码现在,没有隐式作为参数传入,解决方案必须编译一次,然后键入擦除踢,留下最好的隐式硬编码值适合这里。 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
外部与其内部之间没有此类通信发生,因此无论在那里解决了什么,都将应用于传递的所有内容。
当转换发生在隐式 class 声明中时,编译器无法选择正确的隐式转换方法。在下面的示例中,我有一个 Foo[T]
class 和一个隐式的 Helper
class,它采用 Foo
并提供 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
- JDK1.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]
。必须解决相同的代码现在,没有隐式作为参数传入,解决方案必须编译一次,然后键入擦除踢,留下最好的隐式硬编码值适合这里。 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
外部与其内部之间没有此类通信发生,因此无论在那里解决了什么,都将应用于传递的所有内容。