当方法尝试访问不存在的案例 Class 成员时,Scala 隐式方法编译
Scala Implicit Method Compilation when Method Tries to Access Non-Existing Case Class Members
我 运行 使用隐式方法解决了一个 Scala 编译器问题。场景很简单。隐式方法的任务是将 case class A 的对象变成 case class B 的对象。隐式方法实现访问 A 的 case class 成员,它做不存在。如果 case class 成员在 case class A 或 B(例如 foobar)中根本不存在,编译器会抛出错误。如果 case class 成员在 case class B 中确实存在,编译器不会抛出错误,即使我使用此名称访问 case class A(即成员 x)。
我使用的是 2.13.1 版的 Scala。目前,2.13.2 是最新版本。
以下代码更详细地显示了该场景。以下代码会导致编译错误。
package Hokuspokus
object ImplicitMagic extends App {
case class A(a: String, b: String, c: String)
case class B(d: String, e: String, f: String, x: String)
implicit def AtoB: A => B = a => B(a.a, a.b, a.c, a.foobar)
def print(b: B): Unit = {
System.out.println("Print" + b.d)
}
val a = A("foo", "bar", "asdf")
print(a)
}
编译器指出以下错误:
[ERROR] implicit def AtoB: A => B = a => B(a.a, a.b, a.c, a.foobar)
[ERROR] ^
[ERROR] one error found
但是,即使 x 不是 case class:
的成员,以下代码也不会 运行 编译器错误
package Hokuspokus
object ImplicitMagic extends App {
case class A(a: String, b: String, c: String)
case class B(d: String, e: String, f: String, x: String)
implicit def AtoB: A => B = a => B(a.a, a.b, a.c, a.x)
def print(b: B): Unit = {
System.out.println("Print" + b.d)
}
val a = A("foo", "bar", "asdf")
print(a)
}
我现在想知道,为什么 Scala 编译器在编译时没有检测到这个问题。为了了解scalac编译器的作用,我调查了编译后的scala classes,但到目前为止我还没有得出结论。
package Hokuspokus
object ImplicitMagic extends scala.AnyRef with scala.App {
def this() = { /* compiled code */ }
case class A(a: scala.Predef.String, b: scala.Predef.String, c: scala.Predef.String) extends scala.AnyRef with scala.Product with scala.Serializable {
val a: scala.Predef.String = { /* compiled code */ }
val b: scala.Predef.String = { /* compiled code */ }
val c: scala.Predef.String = { /* compiled code */ }
def copy(a: scala.Predef.String, b: scala.Predef.String, c: scala.Predef.String): Hokuspokus.ImplicitMagic.A = { /* compiled code */ }
override def productPrefix: java.lang.String = { /* compiled code */ }
def productArity: scala.Int = { /* compiled code */ }
def productElement(x: scala.Int): scala.Any = { /* compiled code */ }
override def productIterator: scala.collection.Iterator[scala.Any] = { /* compiled code */ }
def canEqual(x: scala.Any): scala.Boolean = { /* compiled code */ }
override def productElementName(x: scala.Int): java.lang.String = { /* compiled code */ }
override def hashCode(): scala.Int = { /* compiled code */ }
override def toString(): java.lang.String = { /* compiled code */ }
override def equals(x: scala.Any): scala.Boolean = { /* compiled code */ }
}
object A extends scala.runtime.AbstractFunction3[scala.Predef.String, scala.Predef.String, scala.Predef.String, Hokuspokus.ImplicitMagic.A] with java.io.Serializable {
def this() = { /* compiled code */ }
final override def toString(): java.lang.String = { /* compiled code */ }
def apply(a: scala.Predef.String, b: scala.Predef.String, c: scala.Predef.String): Hokuspokus.ImplicitMagic.A = { /* compiled code */ }
def unapply(x[=14=]: Hokuspokus.ImplicitMagic.A): scala.Option[scala.Tuple3[scala.Predef.String, scala.Predef.String, scala.Predef.String]] = { /* compiled code */ }
}
case class B(d: scala.Predef.String, e: scala.Predef.String, f: scala.Predef.String, x: scala.Predef.String) extends scala.AnyRef with scala.Product with scala.Serializable {
val d: scala.Predef.String = { /* compiled code */ }
val e: scala.Predef.String = { /* compiled code */ }
val f: scala.Predef.String = { /* compiled code */ }
val x: scala.Predef.String = { /* compiled code */ }
def copy(d: scala.Predef.String, e: scala.Predef.String, f: scala.Predef.String, x: scala.Predef.String): Hokuspokus.ImplicitMagic.B = { /* compiled code */ }
override def productPrefix: java.lang.String = { /* compiled code */ }
def productArity: scala.Int = { /* compiled code */ }
def productElement(x: scala.Int): scala.Any = { /* compiled code */ }
override def productIterator: scala.collection.Iterator[scala.Any] = { /* compiled code */ }
def canEqual(x: scala.Any): scala.Boolean = { /* compiled code */ }
override def productElementName(x: scala.Int): java.lang.String = { /* compiled code */ }
override def hashCode(): scala.Int = { /* compiled code */ }
override def toString(): java.lang.String = { /* compiled code */ }
override def equals(x: scala.Any): scala.Boolean = { /* compiled code */ }
}
object B extends scala.runtime.AbstractFunction4[scala.Predef.String, scala.Predef.String, scala.Predef.String, scala.Predef.String, Hokuspokus.ImplicitMagic.B] with java.io.Serializable {
def this() = { /* compiled code */ }
final override def toString(): java.lang.String = { /* compiled code */ }
def apply(d: scala.Predef.String, e: scala.Predef.String, f: scala.Predef.String, x: scala.Predef.String): Hokuspokus.ImplicitMagic.B = { /* compiled code */ }
def unapply(x[=14=]: Hokuspokus.ImplicitMagic.B): scala.Option[scala.Tuple4[scala.Predef.String, scala.Predef.String, scala.Predef.String, scala.Predef.String]] = { /* compiled code */ }
}
implicit def AtoB: scala.Function1[Hokuspokus.ImplicitMagic.A, Hokuspokus.ImplicitMagic.B] = { /* compiled code */ }
def print(b: Hokuspokus.ImplicitMagic.B): scala.Unit = { /* compiled code */ }
val a: Hokuspokus.ImplicitMagic.A = { /* compiled code */ }
}
编译器正在执行多项操作来解决缺失的 method/val a.foobar
。
它将检查此方法是否属于 case class A
,它将检查 A 是否可以隐式转换为包含方法 foobar
的不同类型,或者是否存在 implicit class
添加方法 foobar
。
最终,它决定此方法不可用,因此您会看到编译器错误。
如果您使用 a.x
,编译器会找到从 A
到 B
的隐式转换,它提供 method/val x
。不幸的是,它没有捕捉到这种情况发生在实际转换中的事实。编译器在这种情况下所做的如下
implicit def AtoB: A => B = a => B(a.a, a.b, a.c, AtoB(a).x)
这会编译,但会在运行时产生 StackOveflowException。
我 运行 使用隐式方法解决了一个 Scala 编译器问题。场景很简单。隐式方法的任务是将 case class A 的对象变成 case class B 的对象。隐式方法实现访问 A 的 case class 成员,它做不存在。如果 case class 成员在 case class A 或 B(例如 foobar)中根本不存在,编译器会抛出错误。如果 case class 成员在 case class B 中确实存在,编译器不会抛出错误,即使我使用此名称访问 case class A(即成员 x)。
我使用的是 2.13.1 版的 Scala。目前,2.13.2 是最新版本。
以下代码更详细地显示了该场景。以下代码会导致编译错误。
package Hokuspokus
object ImplicitMagic extends App {
case class A(a: String, b: String, c: String)
case class B(d: String, e: String, f: String, x: String)
implicit def AtoB: A => B = a => B(a.a, a.b, a.c, a.foobar)
def print(b: B): Unit = {
System.out.println("Print" + b.d)
}
val a = A("foo", "bar", "asdf")
print(a)
}
编译器指出以下错误:
[ERROR] implicit def AtoB: A => B = a => B(a.a, a.b, a.c, a.foobar)
[ERROR] ^
[ERROR] one error found
但是,即使 x 不是 case class:
的成员,以下代码也不会 运行 编译器错误package Hokuspokus
object ImplicitMagic extends App {
case class A(a: String, b: String, c: String)
case class B(d: String, e: String, f: String, x: String)
implicit def AtoB: A => B = a => B(a.a, a.b, a.c, a.x)
def print(b: B): Unit = {
System.out.println("Print" + b.d)
}
val a = A("foo", "bar", "asdf")
print(a)
}
我现在想知道,为什么 Scala 编译器在编译时没有检测到这个问题。为了了解scalac编译器的作用,我调查了编译后的scala classes,但到目前为止我还没有得出结论。
package Hokuspokus
object ImplicitMagic extends scala.AnyRef with scala.App {
def this() = { /* compiled code */ }
case class A(a: scala.Predef.String, b: scala.Predef.String, c: scala.Predef.String) extends scala.AnyRef with scala.Product with scala.Serializable {
val a: scala.Predef.String = { /* compiled code */ }
val b: scala.Predef.String = { /* compiled code */ }
val c: scala.Predef.String = { /* compiled code */ }
def copy(a: scala.Predef.String, b: scala.Predef.String, c: scala.Predef.String): Hokuspokus.ImplicitMagic.A = { /* compiled code */ }
override def productPrefix: java.lang.String = { /* compiled code */ }
def productArity: scala.Int = { /* compiled code */ }
def productElement(x: scala.Int): scala.Any = { /* compiled code */ }
override def productIterator: scala.collection.Iterator[scala.Any] = { /* compiled code */ }
def canEqual(x: scala.Any): scala.Boolean = { /* compiled code */ }
override def productElementName(x: scala.Int): java.lang.String = { /* compiled code */ }
override def hashCode(): scala.Int = { /* compiled code */ }
override def toString(): java.lang.String = { /* compiled code */ }
override def equals(x: scala.Any): scala.Boolean = { /* compiled code */ }
}
object A extends scala.runtime.AbstractFunction3[scala.Predef.String, scala.Predef.String, scala.Predef.String, Hokuspokus.ImplicitMagic.A] with java.io.Serializable {
def this() = { /* compiled code */ }
final override def toString(): java.lang.String = { /* compiled code */ }
def apply(a: scala.Predef.String, b: scala.Predef.String, c: scala.Predef.String): Hokuspokus.ImplicitMagic.A = { /* compiled code */ }
def unapply(x[=14=]: Hokuspokus.ImplicitMagic.A): scala.Option[scala.Tuple3[scala.Predef.String, scala.Predef.String, scala.Predef.String]] = { /* compiled code */ }
}
case class B(d: scala.Predef.String, e: scala.Predef.String, f: scala.Predef.String, x: scala.Predef.String) extends scala.AnyRef with scala.Product with scala.Serializable {
val d: scala.Predef.String = { /* compiled code */ }
val e: scala.Predef.String = { /* compiled code */ }
val f: scala.Predef.String = { /* compiled code */ }
val x: scala.Predef.String = { /* compiled code */ }
def copy(d: scala.Predef.String, e: scala.Predef.String, f: scala.Predef.String, x: scala.Predef.String): Hokuspokus.ImplicitMagic.B = { /* compiled code */ }
override def productPrefix: java.lang.String = { /* compiled code */ }
def productArity: scala.Int = { /* compiled code */ }
def productElement(x: scala.Int): scala.Any = { /* compiled code */ }
override def productIterator: scala.collection.Iterator[scala.Any] = { /* compiled code */ }
def canEqual(x: scala.Any): scala.Boolean = { /* compiled code */ }
override def productElementName(x: scala.Int): java.lang.String = { /* compiled code */ }
override def hashCode(): scala.Int = { /* compiled code */ }
override def toString(): java.lang.String = { /* compiled code */ }
override def equals(x: scala.Any): scala.Boolean = { /* compiled code */ }
}
object B extends scala.runtime.AbstractFunction4[scala.Predef.String, scala.Predef.String, scala.Predef.String, scala.Predef.String, Hokuspokus.ImplicitMagic.B] with java.io.Serializable {
def this() = { /* compiled code */ }
final override def toString(): java.lang.String = { /* compiled code */ }
def apply(d: scala.Predef.String, e: scala.Predef.String, f: scala.Predef.String, x: scala.Predef.String): Hokuspokus.ImplicitMagic.B = { /* compiled code */ }
def unapply(x[=14=]: Hokuspokus.ImplicitMagic.B): scala.Option[scala.Tuple4[scala.Predef.String, scala.Predef.String, scala.Predef.String, scala.Predef.String]] = { /* compiled code */ }
}
implicit def AtoB: scala.Function1[Hokuspokus.ImplicitMagic.A, Hokuspokus.ImplicitMagic.B] = { /* compiled code */ }
def print(b: Hokuspokus.ImplicitMagic.B): scala.Unit = { /* compiled code */ }
val a: Hokuspokus.ImplicitMagic.A = { /* compiled code */ }
}
编译器正在执行多项操作来解决缺失的 method/val a.foobar
。
它将检查此方法是否属于 case class A
,它将检查 A 是否可以隐式转换为包含方法 foobar
的不同类型,或者是否存在 implicit class
添加方法 foobar
。
最终,它决定此方法不可用,因此您会看到编译器错误。
如果您使用 a.x
,编译器会找到从 A
到 B
的隐式转换,它提供 method/val x
。不幸的是,它没有捕捉到这种情况发生在实际转换中的事实。编译器在这种情况下所做的如下
implicit def AtoB: A => B = a => B(a.a, a.b, a.c, AtoB(a).x)
这会编译,但会在运行时产生 StackOveflowException。