Почему эта PartialFunction определена, но все равно вылетает (правильно) после приложения в Scala?

Я хотел бы попробовать частичные функции с вариантом использования с глубоким сопоставлением шаблонов. Первоначально (конечно) это не работало после применения Some(Some(3)), но вместо этого казалось определенным:

def deepTest : PartialFunction [Option[Option[Int]], Int] = {
    case Some(v) => v match {
      case None => 3 
    }
    case None => 1
}

и я подумал, что, отделив вложенное сопоставление с образцом, все будет проще:

def deepTestLvl1 : PartialFunction [Option[Option[Int]], Option[Int]] = {
  case Some(v) => v
  case None => Some(1)
}


def deepTestLvl2 : PartialFunction [Option[Int], Int] = {
  case None => 3
}

но в результате получилось следующее:

scala> (deepTestLvl1 andThen deepTestLvl2) isDefinedAt(Some(Some(3)))
res24: Boolean = true

и после применения:

scala> (deepTestLvl1 andThen deepTestLvl2) (Some(Some(3)))
scala.MatchError: Some(3) (of class scala.Some)
    at scala.PartialFunction$$anon$1.apply(PartialFunction.scala:248)
    at scala.PartialFunction$$anon$1.apply(PartialFunction.scala:246)
    at $anonfun$deepTestLvl2$1.applyOrElse(<console>:7)
    at $anonfun$deepTestLvl2$1.applyOrElse(<console>:7)
        ....
    at scala.tools.nsc.MainGenericRunner.runTarget$1(MainGenericRunner.scala:83)
    at scala.tools.nsc.MainGenericRunner.process(MainGenericRunner.scala:96)
    at scala.tools.nsc.MainGenericRunner$.main(MainGenericRunner.scala:105)
    at scala.tools.nsc.MainGenericRunner.main(MainGenericRunner.scala)

Я что-то делаю неправильно? Разве isDefinedAt не должен вызываться дважды, когда я последовательно составлял deepTestLvl{1,2} и давал мне правильный ответ?


person Aggelos Biboudis    schedule 23.11.2013    source источник
comment
К сожалению, это не задокументировано должным образом в спецификации языка Scala - см. мою аномальную частичную функцию, которая работает но на самом деле не полностью объяснено. Кто-нибудь знает реальную ссылку, которая действительно правильно документирует текущее поведение частичных функций Scala?   -  person Robin Green    schedule 23.11.2013
comment
@RobinGreen Пожалуйста, проверьте мой ответ. Это не задокументировано, потому что это не то, как определяется andThen.   -  person Akos Krivachy    schedule 23.11.2013
comment
@krivachy.akos Я имел в виду deepTest.   -  person Robin Green    schedule 23.11.2013
comment
Ах, в этом случае, я думаю, вам придется реализовать все комбинации двух частичных функций. case Some(None) => 3 и case Some(Some(v)) => v. Это не слишком элегантно, особенно учитывая тот факт, что количество случаев может расти в геометрической прогрессии.   -  person Akos Krivachy    schedule 23.11.2013


Ответы (2)


Очень хороший вопрос.

Давайте проверим источник и посмотрим, что происходит под обложками:

override def andThen[C](k: B => C): PartialFunction[A, C] =
  new AndThen[A, B, C] (this, k)

Здесь мы можем заметить, что andThen даже не ожидает частичной функции, подойдет любая функция, которая преобразует результат. Ваш код работает, потому что: trait PartialFunction[-A, +B] extends (A => B). На самом деле это можно найти в документации:

def andThen[C](k: (B) ⇒ C): PartialFunction[A, C]

Объединяет эту частичную функцию с функцией преобразования, которая применяется к результатам этой частичной функции.

C тип результата функции преобразования.

k функция преобразования

возвращает частичную функцию с тем же доменом, что и эта частичная функция, которая сопоставляет аргументы x с k(this(x)).

Таким образом, в настоящее время нет способа связать PartialFunction так, как вам хотелось бы, потому что, как сказал Робин, для этого потребуется применить функцию. Помимо вычислительных затрат, это также может иметь побочные эффекты, что является более серьезной проблемой.

Обновлять

Собрал реализацию, которую вы ищете. Используйте его осторожно! Как я уже упоминал, если ваш код имеет побочные эффекты, это вызовет проблемы:

implicit class PartialFunctionExtension[-A, B](pf: PartialFunction[A, B]) {
  def andThenPf[C](pf2: PartialFunction[B, C]) = new PfAndThen(pf, pf2)

  class PfAndThen[+C](pf: PartialFunction[A, B], nextPf: PartialFunction[B, C]) extends PartialFunction[A, C] {
    def isDefinedAt(x: A) = pf.isDefinedAt(x) && nextPf.isDefinedAt(pf.apply(x))

    def apply(x: A): C = nextPf(pf(x))
  }
}

Пробуем:

deepTestLvl1.andThenPf(deepTestLvl2).isDefinedAt(Some(Some(3)))  // false
deepTestLvl1.andThenPf(deepTestLvl2).isDefinedAt(Some(None))     // true
deepTestLvl1.andThenPf(deepTestLvl2).apply(Some(None))           // 3
person Akos Krivachy    schedule 23.11.2013

Причина, по которой isDefinedAt для PartialFunction, созданного andThen, возвращает противоречивые результаты, заключается в том, что на самом деле он не применяет первую частичную функцию к своему аргументу, что может быть дорогостоящей операцией.

Такое поведение может сбить людей с толку и не задокументировано — вы можете отправить исправление, чтобы добавить документацию для этого.

P.S. Я предполагаю, что причина того, что deepTest ведет себя так, заключается в том, что самое внешнее match в исходном коде частичного определения функции и только самое внешнее совпадение рассматривается для целей определенности, но вы мне нужно проверить исходный код scalac, чтобы быть уверенным, я думаю.

person Robin Green    schedule 23.11.2013
comment
В разделе «Принципы реактивного программирования» на Coursera Мартин Одерски объясняет, что на самом деле учитывается только самое внешнее совпадение. - person andi5; 24.11.2013