【问题标题】:Scala function composition totalFn(partialFn(totalFn(x)))Scala函数组合totalFn(partialFn(totalFn(x)))
【发布时间】:2019-03-19 14:34:02
【问题描述】:

我试图组合三个函数,只有中间一个是 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,因为输入在第一个函数的域内。

谢谢, 诺伯特

【问题讨论】:

    标签: scala partialfunction partial-functions


    【解决方案1】:

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

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

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

    我的猜测是 Scala 语言设计团队更喜欢自 PartialFunction is a subclass of 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
    

    【讨论】:

    • 谢谢利奥!这将是该语言的一个很好的扩展:) 我一定会开始使用它。
    • 仅供参考@norbertk,我已经扩展了答案以涵盖使用andThen 将总函数与部分函数组合在一起的情况。
    • 阅读您的第一篇文章后,我创建了一个包含 andThen 和 compose 的类。我只是没想过创建隐式类来解决这个问题,但这是添加缺失功能的好方法。 ?
    【解决方案2】:

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

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

    如果它等于 0 那么这就是匹配错误的原因..

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

    希望对你有帮助。

    【讨论】:

    • 感谢雷克斯的回复。我知道 MatchError 来自哪里。问题不在于它被抛出,而是结果类型不是 PartialFunction,即我不能使用.lift 将结果转换为Option[T]。没有有意义的默认值,最终函数calc 应该抛出一个 MatchError。
    • def calc(x: Int)(a: PartialFunction[Int, Double])(c: Double) = mod10 andThen inverse andThen Triple
    • 这样可以用吗?
    • 我不认为代码意味着你想要它的意思。后两个参数在做什么(ac)?
    【解决方案3】:

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

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

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

    【讨论】:

      猜你喜欢
      • 2014-03-28
      • 1970-01-01
      • 1970-01-01
      • 2017-02-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多