Классы абстрагирования - PullRequest
       20

Классы абстрагирования

3 голосов
/ 12 февраля 2012

Я изучаю способы абстрагирования 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 Ответ

1 голос
/ 03 марта 2012

Проблема в этом сопоставителе:

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