我正在探索在Scala中抽象Case Classes的方法。例如,这是Either[Int, String]
的尝试(使用Scala 2.10.0-M1和-Yvirtpatmat
):
trait ApplyAndUnApply[T, R] extends Function1[T, R] {
def unapply(r: R): Option[T]
}
trait Module {
type EitherIntOrString
type Left <: EitherIntOrString
type Right <: EitherIntOrString
val Left: ApplyAndUnApply[Int, Left]
val Right: ApplyAndUnApply[String, Right]
}
鉴于这个定义,我可以写出类似的东西:
def foo[M <: Module](m: M)(intOrString: m.EitherIntOrString): Unit = {
intOrString match {
case m.Left(i) => println("it's an int: "+i)
case m.Right(s) => println("it's a string: "+s)
}
}
以下是该模块的第一个实现,其中Either
的表示形式为String
:
object M1 extends Module {
type EitherIntOrString = String
type Left = String
type Right = String
object Left extends ApplyAndUnApply[Int, Left] {
def apply(i: Int) = i.toString
def unapply(l: Left) = try { Some(l.toInt) } catch { case e: NumberFormatException => None }
}
object Right extends ApplyAndUnApply[String, Right] {
def apply(s: String) = s
def unapply(r: Right) = try { r.toInt; None } catch { case e: NumberFormatException => Some(r) }
}
}
unapply
使Left
和Right
真正排他,所以以下方法可以正常运行:
scala> foo(M1)("42")
it's an int: 42
scala> foo(M1)("quarante-deux")
it's a string: quarante-deux
到目前为止一切顺利。我的第二次尝试是使用scala.Either[Int, String]
作为Module.EitherIntOrString
的自然实现:
object M2 extends Module {
type EitherIntOrString = Either[Int, String]
type Left = scala.Left[Int, String]
type Right = scala.Right[Int, String]
object Left extends ApplyAndUnApply[Int, Left] {
def apply(i: Int) = scala.Left(i)
def unapply(l: Left) = scala.Left.unapply(l)
}
object Right extends ApplyAndUnApply[String, Right] {
def apply(s: String) = scala.Right(s)
def unapply(r: Right) = scala.Right.unapply(r)
}
}
但是这不能按预期工作:
scala> foo(M2)(Left(42))
it's an int: 42
scala> foo(M2)(Right("quarante-deux"))
java.lang.ClassCastException: scala.Right cannot be cast to scala.Left
有没有办法获得正确的结果?
答案 0 :(得分:1)
问题在于这个匹配器:
intOrString match {
case m.Left(i) => println("it's an int: "+i)
case m.Right(s) => println("it's a string: "+s)
}
它无条件地在m.Left.unapply
上执行intOrString
。至于它为什么会这样,见下文。
当你致电foo(M2)(Right("quarante-deux"))
时,这就是正在发生的事情:
m.Left.unapply
解析为M2.Left.unapply
,实际上是scala.Left.unapply
intOrString
是Right("quarante-deux")
因此,在scala.Left.unapply
上调用Right("quarante-deux")
会导致CCE。
现在,为什么会这样。当我尝试通过解释器运行您的代码时,我收到了这些警告:
<console>:21: warning: abstract type m.Left in type pattern m.Left is unchecked since it is eliminated by erasure
case m.Left(i) => println("it's an int: "+i)
^
<console>:22: warning: abstract type m.Right in type pattern m.Right is unchecked since it is eliminated by erasure
case m.Right(s) => println("it's a string: "+s)
^
unapply
的{{1}}方法被删除为ApplyAndUnApply
。由于无法运行类似Option unapply(Object)
的内容(因为intOrString instanceof m.Left
也被擦除),编译器会编译此匹配以运行所有已擦除的m.Left
。
获得正确结果的一种方法如下(不确定它是否符合您最初抽象案例类的想法):
unapply