Классы абстрагирования
Я изучаю способы абстрагирования Case-классов в 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
с 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
Есть ли способ получить правильный результат?
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
метод ApplyAndUnApply
стирается в Option unapply(Object)
, Поскольку невозможно запустить что-то вроде intOrString instanceof m.Left
(так как m.Left
тоже стирается), компилятор компилирует это совпадение для запуска всех стертых unapply
s.
Ниже приведен один из способов получить правильный результат (не уверен, что он согласуется с вашей первоначальной идеей абстрагирования классов 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
}
}