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)
  }
}

これがモジュールの最初の実装です。ここで、の表現EitherString:です。

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) }
  }
}

unapplyLeftRight本当に排他的にするので、以下は期待どおりに機能します。

scala> foo(M1)("42")
it's an int: 42

scala> foo(M1)("quarante-deux")
it's a string: quarante-deux

ここまでは順調ですね。私の2番目の試みは、次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

正しい結果を得る方法はありますか?

4

1 に答える 1

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.unapplyM2.Left.unapply実際に解決しますscala.Left.unapply
  • intOrStringRight("quarante-deux")

その結果、CCEを引き起こすscala.Left.unapplyが呼び出されます。Right("quarante-deux")

さて、なぜこれが起こるのか。インタプリタを介してコードを実行しようとすると、次の警告が表示されました。

<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メソッドはApplyAndUnApplyに消去されOption unapply(Object)ます。intOrString instanceof m.Left(消去されるため)のようなものを実行することは不可能であるためm.Left、コンパイラはこの一致をコンパイルして、消去されたすべてunapplyのを実行します。

正しい結果を得る1つの方法は、以下のとおりです(ケースクラスを抽象化するという元のアイデアと一致するかどうかはわかりません)。

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
    }
}
于 2012-03-03T02:45:53.633 に答える