Чередование частичных функций с andThen в Scala
Давайте использовать примеры из Daily Scala:
type PF = PartialFunction[Int,Int]
val pf1 : PF = {case 1 => 2}
val pf2 : PF = {case 2 => 3}
и давайте добавим:
val pf3 : PF = {case 3 => 4}
и тогда работает как положено здесь:
pf1 andThen pf2 isDefinedAt(x)
возвращается true
тогда и только тогда x == 1
(на самом деле, pf2
не должен быть PartialFunction вообще)
Однако я ожидал, что:
pf1 andThen pf3 isDefinedAt(x)
вернется false
для всех x
(т. е. если pf1 определен, проверьте pf3), но он не проверяет и только проверяет pf1.
В конце, pf1 andThen pf3 lift(x)
всегда приводить к MatchError. Я предпочел бы получить None... Я могу получить это поведение, подняв каждую функцию, например, в pf1.lift(x).flatMap(pf3.lift)
но есть ли более простой способ использования чистого API PartialFunction? (и не поднимая каждую частичную функцию в отдельности?)
3 ответа
Если вы посмотрите на andThen
:
def andThen[C](k: (B) => C): PartialFunction[A, C]
Это составляет приемник с функцией, а не частичной функцией. То есть, k
как ожидается, будет полностью определен, он не имеет isDefinedAt
, Следовательно, результирующая частичная функция не должна изменять поведение isDefinedAt
, он все еще просто должен обратиться к первой частичной функции.
Вы можете написать собственное расширение, которое состоит из двух частичных функций:
implicit class ComposePartial[A, B](pf: PartialFunction[A, B]) {
def collect[C](that: PartialFunction[B, C]): PartialFunction[A, C] =
new PartialFunction[A, C] {
def apply(a: A): C = that(pf(a))
def isDefinedAt(a: A) = pf.isDefinedAt(a) && {
val b = pf(a)
that.isDefinedAt(b)
}
}
}
pf1 collect pf2 isDefinedAt(1) // true
pf1 collect pf3 isDefinedAt(1) // false
Проблема в том, что вы должны вызвать pf(a)
Таким образом, учитывая, что Scala не обеспечивает чистоту, вы можете в конечном итоге выполнять побочные эффекты нежелательно.
Вам нужен эквивалент flatMap
за PartialFunction
s.
implicit class CollectPartial[A, B](f: PartialFunction[A, B]) {
def collect[C](g: PartialFunction[B, C]) = Function.unlift { a: A =>
f.lift(a).flatMap(g.lift)
}
}
Используйте это как
val a: PartialFunction[String, Int] = ...
val b: PartialFunction[Int, Char] = ...
val c: PartialFunction[String, Char] = a collect b
Это работает, как ожидается, даже с побочными эффектами.
Почему бы просто:
def compose[A,B,C](f: PartialFunction[A, B], g: PartialFunction[B, C]) : PartialFunction[A, C] =
Function.unlift(f.andThen(g.lift))