我正在探索在Scala中抽象案例类的方法。例如,这是尝试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使得LeftRight真正互斥,因此以下各项按预期工作:
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

有没有办法得到正确的结果?

最佳答案

问题出在这个匹配器中:

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
  • intOrStringRight("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)
                       ^
    
    unapplyApplyAndUnApply方法被擦除为Option unapply(Object)。由于不可能运行类似intOrString instanceof m.Left的内容(因为m.Left也被删除了),编译器会编译此匹配项以运行所有已删除的unapply

    下面是获得正确结果的一种方法(不确定它是否与您抽象案例类的原始想法一致):
    trait Module {
        type EitherIntOrString
        type Left <: EitherIntOrString
        type Right <: EitherIntOrString
        val L: ApplyAndUnApply[Int, EitherIntOrString]
        val R: ApplyAndUnApply[String, EitherIntOrString]
    }
    
    object M2 extends Module {
        type EitherIntOrString = Either[Int, String]
        type Left = scala.Left[Int, String]
        type Right = scala.Right[Int, String]
        object L extends ApplyAndUnApply[Int, EitherIntOrString] {
            def apply(i: Int) = Left(i)
            def unapply(l: EitherIntOrString) = if (l.isLeft) Left.unapply(l.asInstanceOf[Left]) else None
        }
        object R extends ApplyAndUnApply[String, EitherIntOrString] {
            def apply(s: String) = Right(s)
            def unapply(r: EitherIntOrString) = if (r.isRight) Right.unapply(r.asInstanceOf[Right]) else None
        }
    }
    

    关于scala - 抽象案例类,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/9251007/

    10-12 06:09