Пример 1. Какова причина возврата функции итога, когда хотя бы одна из функций в композиции является частичной?
Это потому, что первая функция в вашем первом примере - это функция итога (Function1), а ее метод andThen возвращает Function1 независимо от того, является ли вторая функция полной или частичной:
def andThen[A](g: (R) => A): (T1) => A
Я предполагаю, что команда разработчиков языка Scala предпочитает более обобщенное возвращаемое значение, поскольку PartialFunction является подклассом функции и скорее позволит пользователям получать специализированный код по мере необходимости.
Пример 2: вызов calc.lift (0.5) вызовет MathError вместо возврата None
Из документа PartialFunction API doc , составление двух частичных функций через andThen
вернет частичную функцию с тем же доменом, что и первая частичная функция:
def andThen[C](k: (B) => C): PartialFunction[A, C]
Таким образом, результирующая составная функция не учитывает тот факт, что inverse(0.5)
(т.е. 2.0) находится вне области второй частичной функции arcSin
.
Итак, при составлении функции (полной или частичной) с частичной функцией с использованием andThen
, как мы можем заставить ее возвращать частичную функцию с соответствующей областью?
Подобно тому, что продемонстрировано в этом SO Q & A , можно улучшить andThen
с помощью пары неявных классов, чтобы ограничить область результирующей составной функции подмножеством области первой функции, которая возвращает значения в пределах области частичной функции:
object ComposeFcnOps {
implicit class TotalCompose[A, B](f: Function[A, B]) {
def andThenPartial[C](that: PartialFunction[B, C]): PartialFunction[A, C] =
Function.unlift(x => Option(f(x)).flatMap(that.lift))
}
implicit class PartialCompose[A, B](pf: PartialFunction[A, B]) {
def andThenPartial[C](that: PartialFunction[B, C]): PartialFunction[A, C] =
Function.unlift(x => pf.lift(x).flatMap(that.lift))
}
}
Тестирование с примерами функций:
import ComposeFcnOps._
val mod10: Int => Int = _ % 10
val inverse1: PartialFunction[Int, Double] = { case n if n != 0 => 1.0 / n }
val triple: Double => Double = _ * 3
val calc1 = mod10 andThenPartial inverse1 andThen triple
// calc1: PartialFunction[Int,Double] = <function1>
calc1.isDefinedAt(0)
// res1: Boolean = false
val inverse2: PartialFunction[Double, Double] = { case n if n != 0 => 1.0 / n }
val arcSin: PartialFunction[Double, Double] = {
case n if math.abs(n) <= 1 => math.asin(n)
}
val calc2 = inverse2 andThenPartial arcSin
// calc2: PartialFunction[Double,Double] = <function1>
calc2.isDefinedAt(0.5)
// res2: Boolean = false
calc2.lift(0.5)
// res3: Option[Double] = None