PartialFunction如何处理Scala中未定义的值?

Hon*_*hen 3 functional-programming scala

PartialFunction在Scala中定义了2个版本.

  val doubleEvens1: PartialFunction[Int, Int] = {
    case x if x % 2 == 0 => x * 2
  }

  val doubleEvens2 = new PartialFunction[Int, Int] {
    override def isDefinedAt(x: Int): Boolean = x % 2 == 0

    override def apply(v1: Int): Int = v1 * 2
  }
Run Code Online (Sandbox Code Playgroud)

和一份清单:

val list = List(1, 2, 3, 4, 5)
Run Code Online (Sandbox Code Playgroud)

但是他们的行为为未定义的值区分:

// doubleEvens1
println(doubleEvens1.isDefinedAt(3)) // false
println(list.collect(doubleEvens1))  // List(4, 8)
println(doubleEvens1(3))             // scala.MatchError
println(list.map(doubleEvens1))      // scala.MatchError

// doubleEvens2
println(doubleEvens2.isDefinedAt(3)) // false
println(list.collect(doubleEvens2))  // List(4, 8)
println(doubleEvens2(3))             // 6
println(list.map(doubleEvens2))      // List(2, 4, 6, 8, 10)
Run Code Online (Sandbox Code Playgroud)

我想doubleEvens1应该是合理的,因为它符合数学定义.但是doubleEvens2按目的设计的行为是什么?或者我在代码片段中遗漏了什么?

0__*_*0__ 5

如果您实现自己的apply方法而不进行检查isDefinedAt,那么显然您可以使用任何输入调用该方法,而不会抛出异常.这在文档中明确说明:

调用者有责任在调用isDefinedAt之前调用apply,因为如果isDefinedAt为false,则不保证apply会抛出异常来指示错误情况.如果未抛出异常,则评估可能会导致任意值.

这恰好从一个模式匹配制成的部分功能调用isDefinedAt本身从它的apply方法实现,并抛出一个异常.

因此,如果要复制该行为,则必须执行以下操作:

val doubleEvens2 = new PartialFunction[Int, Int] {
  override def isDefinedAt(x: Int): Boolean = x % 2 == 0

  override def apply(x: Int): Int = 
    if (isDefinedAt(x)) x * 2 else throw new MatchError(x.toString)
}
Run Code Online (Sandbox Code Playgroud)

当然缺点是isDefined可能会多次调用某些code(),如本问题所述.

  • 文档说你 _may_ 在没有 `isDefinedAt` 的情况下定义了 `apply`。简单地说,如果使用站点不检查 `isDefinedAt`,则 `apply` 的行为是未定义的。使用诸如`collect` 之类的采用部分函数的收集方法将检查`isDefined`(如示例调用的每一行所示)。因为`PartialFunction` 是`Function` 的一个子类型,你可能会遇到奇怪的情况,比如`List.map`,它不知道你正在使用一个偏函数,因此不会调用`isDefinedAt`。 (2认同)