Scala 函数组合 totalFn(partialFn(totalFn(x)))

Scala function composition totalFn(partialFn(totalFn(x)))

我试图组合三个函数,只有中间一个是 PartialFunction。我希望结果类型也是 PartialFunction。

示例:

val mod10: Int => Int = _ % 10
val inverse: PartialFunction[Int, Double] = { case n if n != 0 => 1.0 / n }
val triple: Double => Double = _ * 3

val calc: Int => Double = mod10 andThen inverse andThen triple

但是,calc 并未在其整个域上定义。它会为每个可被 10 整除的数字抛出 MatchError。

当组合中至少有一个函数是部分函数时返回总函数的原因是什么?

另一个示例,其中部分函数的组合导致另一个部分函数具有不正确的域条件:

val inverse: 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 calc: PartialFunction[Double, Double] = inverse andThen arcSin

我希望 calc 的域是 (-Infinity, -1] union [1, Infinity) 但是调用 calc.lift(0.5) 会抛出 MathError 而不是返回 None 因为输入在第一个函数的范围内域名。

谢谢, 诺伯特

我认为错误是您唯一期望的非零值。

{ case n if n != 0 => 1.0 / n } 

那么如果它等于零怎么办,这就是匹配错误的原因..

{ 
   case n if n != 0 => 1.0 / n   // non-zero value.
   case n if n == 0 =>           // zero value.

} 

希望对您有所帮助。

Example 1: What is the reason for returning a total function when at least one of the functions in the composition is partial?

这是因为第一个示例中的第一个函数是总函数 (Function1) 并且其 andThen 方法 return无论第二个函数是总函数还是部分函数都是 Function1:

def andThen[A](g: (R) => A): (T1) => A

我的猜测是,Scala 语言设计团队更喜欢 returned 值,因为 PartialFunction is a subclass of Function 并且宁愿让用户根据需要派生专用代码。

Example 2: calling calc.lift(0.5) will throw a MathError instead of returning None

PartialFunction API doc 开始,通过 andThen 组合两个偏函数将 return 一个与第一个偏函数具有相同定义域的偏函数:

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

因此,合成函数忽略了 inverse(0.5)(即 2.0)在第二个偏函数 arcSin.

域之外的事实

那么,当使用 andThen 将一个函数(全部或部分)与部分函数组合时,我们如何才能使它 return 成为具有适当域的部分函数?

类似于此 SO Q&A 中演示的内容,可以通过几个隐式 类 来增强 andThen 以将合成函数的域限制为第一个函数的子集return 值在部分函数域内的函数域:

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

andThen 是在 Function1 上定义的,根本不是为了组合部分函数而设计的。因此,我建议在使用之前将它们提升到总功能。

val calc = Function.unlift(mod10 andThen inverse.lift andThen (_.map(triple)))

val calc = Function.unlift(inverse.lift andThen (_.flatMap(arcSin.lift)))