1

I was trying to compose three functions with only the middle one being a PartialFunction. I would expect the resulting type to be PartialFunction as well.

Example:

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

However, calc is not defined on the whole of its domain. It will throw MatchError for every number divisible by 10.

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

Another example where composition of partial functions results in another partial function with incorrect domain conditions:

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

I would expect the domain of calc to be (-Infinity, -1] union [1, Infinity) but calling calc.lift(0.5) will throw a MathError instead of returning None because the input is within the first function's domain.

Thanks, Norbert

4

3 回答 3

1

示例 1:当组合中的至少一个函数是部分函数时,返回总函数的原因是什么?

这是因为您的第一个示例中的第一个函数是一个总函数 (Function1),并且它的andThen方法返回一个 Function1,无论第二个函数是全部还是部分:

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

我的猜测是 Scala 语言设计团队更喜欢更通用的返回值,因为PartialFunction 是 Function 的子类,宁愿让用户根据需要派生专门的代码。

示例 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
于 2019-03-19T16:44:25.530 回答
1

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

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

val calc = Function.unlift(inverse.lift andThen (_.flatMap(arcSin.lift)))
于 2019-03-19T17:54:22.940 回答
1

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

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

} 

希望能帮助到你。

于 2019-03-19T14:45:09.083 回答