scala PartialFunction偏函数原理示例源码分析
原理
Scala中的PartialFunction
(偏函数)是一种特殊类型的函数,它只对输入域的某个子集定义。换句话说,它只在满足特定条件的输入值上进行操作,而在其他情况下则不进行处理。
PartialFunction
是一个特质(trait),定义如下:
trait PartialFunction[-A, +B] extends (A) => B {
def isDefinedAt(x: A): Boolean
def apply(x: A): B
...
}
其中,-A
表示输入类型,+B
表示输出类型。isDefinedAt
方法用于判断给定的输入值是否在该偏函数的定义域内,apply
方法则定义了实际的操作逻辑。
偏函数可以通过case
语句块来定义,例如:
val divideByZero: PartialFunction[Int, Int] = {
case x if x != 0 => 10 / x
}
在上述例子中,divideByZero
是一个偏函数,它接收一个Int
类型的参数,并返回一个Int
类型的结果。该偏函数定义了当输入值不为零时,计算 10 / x
的操作。
方法总结
函数名字 | 描述 | 参数 | 返回值 |
---|---|---|---|
isDefinedAt(x: A): Boolean | 检查给定的值是否在函数的定义范围内。 | x - 要测试的值。 | 如果x 在函数的定义范围内,则返回true ,否则返回false 。 |
apply(x: A): B | 将给定的参数应用于函数,并返回结果。 | x - 要应用函数的参数。 | 函数应用后的结果。 |
orElse(that: PartialFunction[A1, B1]): PartialFunction[A1, B1] | 将当前偏函数与另一个偏函数组合起来,创建一个新的偏函数,该函数的定义范围为两个偏函数的定义范围的并集。 | that - 另一个偏函数。 | 组合后的新偏函数。 |
andThen[C](k: B => C): PartialFunction[A, C] | 将当前偏函数的结果传递给转换函数,生成一个新的偏函数。 | k - 结果转换函数。 | 转换后的新偏函数。 |
lift: A => Option[B] | 将偏函数转换为普通函数,返回Option 类型的结果。如果输入在偏函数的定义范围内,则返回Some ,否则返回None 。 | 无 | 无 |
applyOrElse[A1 <: A, B1 >: B](x: A1, default: A1 => B1): B1 | 根据输入是否在偏函数的定义范围内,选择调用偏函数的apply 方法或者调用默认的回调函数。 | x - 函数参数,default - 默认回调函数。 | 如果输入在偏函数的定义范围内,则返回偏函数的结果;否则返回回调函数的结果。 |
runWith[U](action: B => U): A => Boolean | 将偏函数与动作函数组合起来,仅运行动作函数来处理偏函数的结果,而忽略返回值。 | action - 动作函数。 | 一个新的函数,该函数接受参数并返回布尔值,表示偏函数是否成功执行了动作函数。 |
cond[T](x: T)(pf: PartialFunction[T, Boolean]): Boolean | 根据给定的值和偏函数,判断值是否在偏函数的定义范围内,并且偏函数的结果为true 。 | x - 要测试的值,pf - 偏函数。 | 如果值在偏函数的定义范围内,并且偏函数的结果为true ,则返回true ,否则返回false 。 |
condOpt[T,U](x: T)(pf: PartialFunction[T, U]): Option[U] | 将偏函数转换为普通函数,根据给定的值返回Option 类型的结果。如果值在偏函数的定义范围内,则返回Some ,否则返回None 。 | 无 | 无 |
empty[A, B]: PartialFunction[A, B] | 一个空的偏函数,没有定义域。 | 无 | 当尝试调用该偏函数时,会抛出MatchError 异常。 |
示例
以下是每种方法的代码示例及其说明:
isDefinedAt(x: A): Boolean
val divideByTwo: PartialFunction[Int, Int] = {
case x if x % 2 == 0 => x / 2
}
println(divideByTwo.isDefinedAt(4)) // 输出: true
println(divideByTwo.isDefinedAt(5)) // 输出: false
在这个示例中,isDefinedAt
方法用于检查给定的值是否在函数的定义范围内。对于偏函数divideByTwo
,它只对偶数进行定义,因此isDefinedAt(4)
返回true
,而isDefinedAt(5)
返回false
。
apply(x: A): B
val divideByTwo: PartialFunction[Int, Int] = {
case x if x % 2 == 0 => x / 2
}
println(divideByTwo(4)) // 输出: 2
在这个示例中,apply
方法用于将给定的参数应用于函数,并返回结果。对于偏函数divideByTwo
,如果输入的值是偶数,则返回其除以2的结果。
orElse(that: PartialFunction[A1, B1]): PartialFunction[A1, B1]
val isEven: PartialFunction[Int, String] = {
case x if x % 2 == 0 => s"$x is even"
}
val isOdd: PartialFunction[Int, String] = {
case x if x % 2 != 0 => s"$x is odd"
}
val numbers: PartialFunction[Int, String] = isEven.orElse(isOdd)
println(numbers(4)) // 输出: 4 is even
println(numbers(5)) // 输出: 5 is odd
在这个示例中,orElse
方法用于将两个偏函数组合起来,创建一个新的偏函数。isEven
偏函数定义了对偶数的处理逻辑,而isOdd
偏函数定义了对奇数的处理逻辑。通过使用orElse
方法,可以创建一个新的偏函数numbers
,该函数对输入值既可以执行isEven
的逻辑,也可以执行isOdd
的逻辑。
andThen[C](k: B => C): PartialFunction[A, C]
val addOne: PartialFunction[Int, Int] = {
case x => x + 1
}
val multiplyByTwo: PartialFunction[Int, Int] = {
case x => x * 2
}
val operation: PartialFunction[Int, String] = addOne.andThen(result => s"The result is $result").andThen(_ + "!")
println(operation(3)) // 输出: The result is 8!
在这个示例中,andThen
方法用于将当前偏函数的结果传递给转换函数,生成一个新的偏函数。addOne
偏函数将输入值加一,multiplyByTwo
偏函数将输入值乘以二。通过使用andThen
方法,我们将新的转换函数应用于结果,得到一个新的偏函数operation
,它将输入值加一并转换为字符串形式,并在末尾添加感叹号。
lift: A => Option[B]
val divideByTwo: PartialFunction[Int, Int] = {
case x if x % 2 == 0 => x / 2
}
val liftedFunction: Int => Option[Int] = divideByTwo.lift
println(liftedFunction(4)) // 输出: Some(2)
println(liftedFunction(5)) // 输出: None
在这个示例中,lift
方法用于将偏函数转换为普通函数,返回Option
类型的结果。对于偏函数divideByTwo
,如果输入值是偶数,则返回Some
包装的除以2的结果;否则返回None
。
applyOrElse[A1 <: A, B1 >: B](x: A1, default: A1 => B1): B1
val divideByTwo: PartialFunction[Int, Int] = {
case x if x % 2 == 0 => x / 2
}
val result1 = divideByTwo.applyOrElse(4, (x: Int) => x * 10)
val result2 = divideByTwo.applyOrElse(5, (x: Int) => x * 10)
println(result1) // 输出: 2
println(result2) // 输出: 50
在这个示例中,applyOrElse
方法根据输入是否在偏函数的定义范围内,选择调用偏函数的apply
方法或者调用默认的回调函数。对于偏函数divideByTwo
,如果输入值是偶数,则返回其除以2的结果;否则使用默认的回调函数,将输入值乘以10。
runWith[U](action: B => U): A => Boolean
val printEven: PartialFunction[Int, Unit] = {
case x if x % 2 == 0 => println(s"$x is even")
}
val checkAndPrint: Int => Boolean = printEven.runWith(result => {
println("Result received: " + result)
})
println(checkAndPrint(4)) // 输出:
// 4 is even
// Result received: ()
// true
println(checkAndPrint(5)) // 输出: false
在这个示例中,runWith
方法将偏函数与动作函数组合起来,仅运行动作函数来处理偏函数的结果,而忽略返回值。对于偏函数printEven
,如果输入值是偶数,则打印出该值是偶数;然后使用runWith
方法将其与一个动作函数组合起来,该动作函数打印出接收到的结果。checkAndPrint
函数被创建为一个新的函数,用于检查输入值是否是偶数并打印相应的消息。
cond[T](x: T)(pf: PartialFunction[T, Boolean]): Boolean
val isEven: PartialFunction[Int, Boolean] = {
case x if x % 2 == 0 => true
}
val isOdd: PartialFunction[Int, Boolean] = {
case x if x % 2 != 0 => true
}
val result1 = PartialFunction.cond(4)(isEven)
val result2 = PartialFunction.cond(5)(isEven)
println(result1) // 输出: true
println(result2) // 输出: false
在这个示例中,cond
方法根据给定的值和偏函数,判断值是否在偏函数的定义范围内,并且偏函数的结果为true
。对于偏函数isEven
,如果输入值是偶数,则返回true
;对于偏函数isOdd
,如果输入值是奇数,则返回true
。通过使用cond
方法,我们可以方便地检查给定的值是否满足特定的条件。
condOpt[T,U](x: T)(pf: PartialFunction[T, U]): Option[U]
val divideByTwo: PartialFunction[Int, Int] = {
case x if x % 2 == 0 => x / 2
}
val result1 = PartialFunction.condOpt(4)(divideByTwo)
val result2 = PartialFunction.condOpt(5)(divideByTwo)
println(result1) // 输出: Some(2)
println(result2) // 输出: None
在这个示例中,condOpt
方法将偏函数转换为普通函数,根据给定的值返回Option
类型的结果。对于偏函数divideByTwo
,如果输入值是偶数,则返回Some
包装的除以2的结果;否则返回None
。通过使用condOpt
方法,我们可以更方便地处理只对特定输入进行定义的情况,并得到相应的结果。
以上是每种方法的代码示例及其说明,展示了PartialFunction
不同方法的使用方式和效果。这些示例可以帮助更好地理解偏函数在实际开发中的应用场景和用法。
中文注释
以下是中文注释版本的PartialFunction
源码:
/**
* 偏函数(Partial Function)是一种输入类型为 A、输出类型为 B 的一元函数,
* 其定义域不一定包含类型 A 的所有值。isDefinedAt 方法可以动态地测试一个值是否在函数的定义域内。
*
* 即使对于某个 a: A,isDefinedAt 返回 true,调用 apply(a) 仍然可能抛出异常,
* 因此下面的代码是合法的:
*
* {{{
* val f: PartialFunction[Int, Any] = { case _ => 1/0 }
* }}}
*
* 调用者有责任在调用 apply 之前先调用 isDefinedAt,因为如果 isDefinedAt 返回 false,
* 并不保证 apply 抛出异常来表示错误条件。如果没有抛出异常,则计算可能会得到任意值。
*
* PartialFunction 和 [[scala.Function1]] 的主要区别在于偏函数的使用者可以选择对其定义域之外的输入执行其他操作。例如:
*
* {{{
* val sample = 1 to 10
* val isEven: PartialFunction[Int, String] = {
* case x if x % 2 == 0 => x+" 是偶数"
* }
*
* // collect 方法可以使用 isDefinedAt 来选择要收集的成员
* val evenNumbers = sample collect isEven
*
* val isOdd: PartialFunction[Int, String] = {
* case x if x % 2 == 1 => x+" 是奇数"
* }
*
* // orElse 方法允许链接另一个偏函数来处理声明域之外的输入
* val numbers = sample map (isEven orElse isOdd)
* }}}
*
* 作者:Martin Odersky, Pavel Pavlov, Adriaan Moors
* 版本:1.0,16/07/2003
*/
trait PartialFunction[-A, +B] extends (A => B) { self =>
import PartialFunction._
/**
* 检查值是否在函数的定义域内。
*
* @param x 要测试的值
* @return 如果 x 在该函数的定义域内,则返回 true,否则返回 false
*/
def isDefinedAt(x: A): Boolean
/**
* 将当前偏函数与另一个偏函数组合起来,
* 创建一个新的偏函数,其定义域为两个偏函数的并集。
*
* @param that 其他偏函数
* @tparam A1 新偏函数的参数类型(是当前偏函数参数类型的子类型)
* @tparam B1 新偏函数的结果类型(是当前偏函数结果类型的超类型)
* @return 一个新的偏函数,它的定义域是当前偏函数和传入的偏函数的并集。
* 对于属于当前偏函数定义域的输入,返回 this(x);
* 对于不属于当前偏函数定义域的输入,返回 that(x)。
*/
def orElse[A1 <: A, B1 >: B](that: PartialFunction[A1, B1]): PartialFunction[A1, B1] =
new OrElse[A1, B1](this, that)
//TODO: 为什么不使用重载来定义 orElse(that: F1): F1?
/**
* 将当前偏函数的结果应用于转换函数,
* 生成一个新的偏函数。
*
* @param k 转换函数
* @tparam C 转换函数的结果类型
* @return 具有与当前偏函数相同定义域的新偏函数,将参数 x 映射到 k(this(x))
*/
override def andThen[C](k: B => C): PartialFunction[A, C] =
new AndThen[A, B, C](this, k)
/**
* 将偏函数转换为返回 Option 结果的普通函数。
* @see Function.unlift
* @return 一个函数,它接受一个参数 x,
* 如果该偏函数在 x 处定义,则返回 Some(this(x));
* 否则返回 None。
*/
def lift: A => Option[B] = new Lifted(this)
/**
* 当函数包含输入值时,将此偏函数应用于给定的参数。
* 在此偏函数未定义的情况下,应用其他回退函数。
*
* 注意,表达式 `pf.applyOrElse(x, default)` 等效于
* {{{ if(pf isDefinedAt x) pf(x) else default(x) }}}
* 除了 applyOrElse 方法可以更高效地实现之外。
* 对于所有偏函数字面量,编译器会生成一个 applyOrElse 实现,
* 它避免了模式匹配和守卫条件的二次评估。
* 这使得 applyOrElse 成为许多操作和场景的高效实现基础,例如:
*
* - 将偏函数组合成 orElse/andThen 链不会导致过多的 apply/isDefinedAt 评估
* - lift 和 unlift 在每次调用时不会两次计算源函数
* - runWith 允许将偏函数与有条件地应用的动作以有效的命令式风格组合
*
* 对于具有非平凡 isDefinedAt 方法的非字面量偏函数类,建议重写 applyOrElse,使用自定义实现来避免二次 isDefinedAt 评估。
* 这可能会提供更好的性能和对副作用的更可预测行为。
*
* @param x 函数参数
* @param default 回退函数
* @return 函数结果或回退函数应用的结果
* @since 2.10
*/
def applyOrElse[A1 <: A, B1 >: B](x: A1, default: A1 => B1): B1 =
if (isDefinedAt(x)) apply(x) else default(x)
/**
* 将此偏函数与一个 action 函数组合起来,
* 该函数在此偏函数的结果上应用。
* action 函数仅用于副作用,其结果被忽略。
*
* 注意,表达式 `pf.runWith(action)(x)` 等效于
* {{{ if(pf isDefinedAt x) { action(pf(x)); true } else false }}}
* 除了 runWith 是通过 applyOrElse 实现的,因此可能更高效。
* 使用 runWith 可以避免针对偏函数字面量的模式匹配和守卫条件的二次评估。
* @see `applyOrElse`
*
* @param action 动作函数
* @return 一个函数,将参数 x 映射到 isDefinedAt(x)。
* 结果函数在 this(x) 已定义时运行 action(this(x))。
* @since 2.10
*/
def runWith[U](action: B => U): A => Boolean = { x =>
val z = applyOrElse(x, checkFallback[B])
if (!fallbackOccurred(z)) { action(z); true } else false
}
}
/**
* PartialFunction 对象提供了一些方便的操作,利用了偏函数中额外的信息。
* 示例:
* {{{
* import PartialFunction._
*
* def strangeConditional(other: Any): Boolean = cond(other) {
* case x: String if x == "abc" || x == "def" => true
* case x: Int => true
* }
* def onlyInt(v: Any): Option[Int] = condOpt(v) { case x: Int => x }
* }}}
*
* 作者:Paul Phillips
* 版本:2.8
*/
object PartialFunction {
/** `PartialFunction#orElse` 方法生成的复合函数 */
private class OrElse[-A, +B](f1: PartialFunction[A, B], f2: PartialFunction[A, B]) extends PartialFunction[A, B] {
def isDefinedAt(x: A) = f1.isDefinedAt(x) || f2.isDefinedAt(x)
def apply(x: A): B = f1.applyOrElse(x, f2)
override def applyOrElse[A1 <: A, B1 >: B](x: A1, default: A1 => B1): B1 = {
val z = f1.applyOrElse(x, checkFallback[B])
if (!fallbackOccurred(z)) z else f2.applyOrElse(x, default)
}
override def orElse[A1 <: A, B1 >: B](that: PartialFunction[A1, B1]) =
new OrElse[A1, B1](f1, f2 orElse that)
override def andThen[C](k: B => C) =
new OrElse[A, C](f1 andThen k, f2 andThen k)
}
/** `PartialFunction#andThen` 方法生成的复合函数 */
private class AndThen[-A, B, +C](pf: PartialFunction[A, B], k: B => C) extends PartialFunction[A, C] {
def isDefinedAt(x: A) = pf.isDefinedAt(x)
def apply(x: A): C = k(pf(x))
override def applyOrElse[A1 <: A, C1 >: C](x: A1, default: A1 => C1): C1 = {
val z = pf.applyOrElse(x, checkFallback[B])
if (!fallbackOccurred(z)) k(z) else default(x)
}
}
/**
* 为了高效地实现类似于 `if(pf isDefinedAt x) f1(pf(x)) else f2(x)` 这样的模式,
* 使用以下技巧:
*
* 为了避免模式匹配和守卫条件的二次评估,此处使用了 `applyOrElse` 方法而不是 `isDefinedAt`/`apply` 组合。
*
* 在调用 `applyOrElse` 后,我们需要函数返回的结果以及函数的参数是否包含在其定义域中的信息。
* 我们唯一可以调整的地方是 `applyOrElse` 方法的继续参数(`default`)。
* 显而易见的方法是从 `default` 函数抛出异常,并在调用 `applyOrElse` 后捕获它,但我认为这种方式效率较低。
*
* 我只知道一种方法可以高效地完成这个任务:`default` 函数应该返回一个唯一的标记对象,该对象在任何其他(常规/偏)函数中都不会返回。
* 这样,在调用 `applyOrElse` 后,您只需要进行一次引用比较即可区分是否 `pf isDefined x`。
*
* 这与特化正确交互,因为 `applyOrElse` 的返回类型(参数化上界)永远不能特化。
*
* 这里的 `fallback_pf` 既是唯一的标记对象,也是返回它的特殊回退函数。
*/
private[this] val fallback_pf: PartialFunction[Any, Any] = { case _ => fallback_pf }
private def checkFallback[B] = fallback_pf.asInstanceOf[PartialFunction[Any, B]]
private def fallbackOccurred[B](x: B) = (fallback_pf eq x.asInstanceOf[AnyRef])
/**
* 将偏函数转换为接受参数类型为 A、返回值类型为 Option[B] 的普通函数。
*/
private class Lifted[-A, +B](val pf: PartialFunction[A, B])
extends scala.runtime.AbstractFunction1[A, Option[B]] {
def apply(x: A): Option[B] = {
val z = pf.applyOrElse(x, checkFallback[B])
if (!fallbackOccurred(z)) Some(z) else None
}
}
/**
* 将函数转换为偏函数。
* @since 2.10
*/
def apply[A, B](f: A => B): PartialFunction[A, B] = { case x => f(x) }
private[this] val constFalse: Any => Boolean = { _ => false }
/**
* 空定义域的偏函数。
* 任何尝试调用空偏函数的操作都会导致抛出 [[scala.MatchError]] 异常。
* @since 2.10
*/
private[this] val empty_pf: PartialFunction[Any, Nothing] = new PartialFunction[Any, Nothing] {
def isDefinedAt(x: Any) = false
def apply(x: Any) = throw new MatchError(x)
override def orElse[A1, B1](that: PartialFunction[A1, B1]) = that
override def andThen[C](k: Nothing => C) = this
override val lift = (x: Any) => None
override def runWith[U](action: Nothing => U) = constFalse
}
/**
* 空定义域的偏函数。
*/
def empty[A, B]: PartialFunction[A, B] = empty_pf
/**
* 创建基于值和偏函数的布尔测试。
* 它类似于带有暗示的 `match` 语句,其后跟随所提供的情况之后的 `case _ => false`。
*
* @param x 要测试的值
* @param pf 偏函数
* @return 如果 `x` 在 `pf` 的定义域内且 `pf(x) == true`,则返回 true;否则返回 false。
*/
def cond[T](x: T)(pf: PartialFunction[T, Boolean]): Boolean = pf.applyOrElse(x, constFalse)
/**
* 将偏函数 `pf` 转换为接受参数类型为 T、返回值类型为 Option[U] 的函数 `f`,
* 并将其应用于值 `x`。实际上,它是一个 `'''match'''` 语句,
* 其中所有的 case 结果都被包装在 `Some(_)` 中,并在末尾添加了 `'''case''' _ => None`。
*
* @param x 要测试的值
* @param pf 偏函数
* @return 如果 `pf isDefinedAt x`,则返回 `Some(pf(x))`;否则返回 None。
*/
def condOpt[T, U](x: T)(pf: PartialFunction[T, U]): Option[U] = pf.lift(x)
}