2012-02-12 60 views
3

我正在探索在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 

有沒有辦法得到正確的結果呢?

回答

1

問題是此匹配:

intOrString match { 
    case m.Left(i) => println("it's an int: "+i) 
    case m.Right(s) => println("it's a string: "+s) 
} 

無條件地對intOrString執行m.Left.unapply。至於它的原因,請參閱下文。

當你調用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) 
       ^

ApplyAndUnApplyunapply方法被擦除Option unapply(Object)。由於不可能運行類似intOrString instanceof m.Left(因爲m.Left也被擦除),所以編譯器編譯該匹配以運行所有擦除的unapplys。

一種方式得到正確的結果低於(如果它與你原來的抽象case類的想法一起去不知道):

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 
    } 
} 
+0

很好的解釋!我決定通過不使用模式匹配來解決我的問題:-)定義摺疊函數更容易,更強大。 – betehess 2012-03-03 22:13:27