Scala函数组合中偏函数的类型与定义域异常问题咨询
关于Scala偏函数组合的两个常见问题解析
嘿,我来帮你拆解这两个Scala偏函数组合时遇到的困惑~
一、为什么含偏函数的组合会返回全函数?
先看你第一个例子里的代码:
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
问题出在**andThen方法的调用主体上**:
mod10是一个普通的全函数(Function1[Int, Int]),它的andThen方法签名是def andThen[A](g: R => A): T => A——不管传入的g是不是偏函数,这个方法都会返回一个全函数。- 当你用全函数调用
andThen时,Scala不会帮你把结果转换成偏函数,它默认你希望得到一个全函数,因此不会自动检查后续偏函数的isDefinedAt条件。
所以当输入被10整除的数时,mod10输出0,直接传给inverse这个偏函数,此时因为没有提前检查inverse.isDefinedAt(0),直接调用就会抛出MatchError。
如果想要得到预期的偏函数,你需要把第一个全函数转换成偏函数,再进行组合:
val mod10PF: PartialFunction[Int, Int] = { case n => n % 10 } val calc: PartialFunction[Int, Double] = mod10PF andThen inverse andThen triple
这时calc就是一个偏函数,调用calc.isDefinedAt(10)会返回false,用calc.lift(10)会得到None,不会抛出异常。
二、为什么组合后的偏函数定义域不符合预期,调用lift会报错?
先看你的第二个例子:
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的定义域是(-∞, -1] ∪ [1, +∞),这个预期是完全正确的——因为calc.isDefinedAt(x)等价于:
inverse.isDefinedAt(x)(也就是x≠0)- 且
arcSin.isDefinedAt(inverse(x))(也就是|1/x| ≤1,即|x|≥1)
那为什么你调用calc.lift(0.5)会抛出异常呢?大概率是你混淆了直接调用偏函数和用lift调用的区别:
- 如果你直接调用
calc(0.5),Scala会先执行inverse(0.5)得到2.0,再尝试把2.0传给arcSin,此时arcSin没有定义,就会抛出MatchError(或者IllegalArgumentException,因为math.asin(2.0)本身也会报错)。 - 但如果你用
calc.lift(0.5),它会先检查calc.isDefinedAt(0.5):因为0.5不满足|x|≥1,所以isDefinedAt返回false,最终会返回None,不会抛出异常。
可能你实际运行时误写了直接调用,而非用lift,才导致了报错。
内容的提问来源于stack exchange,提问作者user2697852
相关产品推荐
相关产品推荐

