Odersky书中的偏函数解释

Partial function explanation in the Odersky book

在 Scala Odersky 书中,他有一个解释第 295 页的部分函数的示例。它从这个函数开始:

val second: List[Int] => Int = {
    case x :: y :: _ => y
}

所以如果你传递一个三元素列表而不是一个空列表,上面的函数将会成功。

second(List(5,6,7))

有效但无效

second(List())

上面会抛出一个MatchError: List

这是让我感到困惑的部分。奥德斯基写道:

If you want to check whether a partial function is defined, you must first tell the compiler that you know you are working with partial functions.

为什么我要检查是否定义了偏函数。什么是偏函数?它是只适用于某些值的函数吗?

The type List[Int] => Int includes all functions from lists of integers to integers, whether or not the functions are partial. The type that only includes partial functions from lists of integers to integers is written PartialFunction[List[Int], Int].

所以上面的函数returns是一个List[Int] => Int类型的函数,我明白了,但是为什么我们需要把这个函数改成PartialFunction[List[Int], Int]类型呢?

这里是重新定义的函数:

val second: PartialFunction[List [Int], Int] = {
    case x :: y :: _ => y
}

我不太明白。有什么好处?为什么我们要检查是否定义了偏函数?那到底是什么意思?

一个部分函数是任何函数,它只接受一个参数,即defined(即有效)只对特定的其参数值的范围。例如,Math.asin 仅针对 [-1.0, 1.0] 范围内的参数值定义,而对于该范围外的值未定义 - 因此它是部分函数。例如,如果我们调用 Math.asin(5.0),我们得到 NaN returned,这意味着该函数没有为该参数定义。

请注意,偏函数不一定要抛出异常;它只需要做一些事情,而不是 return 一个有效值。

函数式编程的一个关键原则是引用透明RT),这意味着我们应该能够用该表达式的值替换表达式(例如函数调用),而不会改变程序的含义。 (有关此主题的更多信息,我强烈建议您阅读 Chiusano 和 Bjarnason 的 Functional Programming in Scala。)显然,如果抛出异常或 returned 无效值,它就会崩溃。对于引用透明的部分函数调用,我们只能使用定义了它们的参数值来调用它们,或者我们需要优雅地处理未定义的值。那么我们如何判断是否为某个任意参数值定义了部分函数?

Scala 中,我们可以将部分函数表示为 scala.PartialFunction 的子类,这使我们能够回答这个问题。

让我们在 Scala REPL 会话中查看您的示例...

$ scala
Welcome to Scala 2.12.6 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_171).
Type in expressions for evaluation. Or try :help.

scala> val second: List[Int] => Int = {
     |     case x :: y :: _ => y
     | }
<console>:11: warning: match may not be exhaustive.
It would fail on the following inputs: List(_), Nil
       val second: List[Int] => Int = {
                                  ^
second: List[Int] => Int = $$Lambda81/1894473818@27492c62

那么我们刚刚做了什么?我们将 second 定义为对采用 List[Int] 参数和 return 一个 Int(列表中的第二个值)的函数的引用。

您会注意到 Scala 编译器 认识到这不会匹配所有情况并警告您这一事实。这是一个部分函数,​​因为它会因某些参数而失败,但它不是 scala.PartialFunction 的实例,我们可以按如下方式验证:

scala> second.isInstanceOf[PartialFunction[List[Int], Int]]
res0: Boolean = false

顺便说一句,List[Int] => Int 类型是 scala.Function1[List[Int], Int] 的 shorthand,因此 seconds 类型是该类型的一个实例:

scala> second.isInstanceOf[Function1[List[Int], Int]]
res1: Boolean = true

调用此版本的函数会产生您指定的结果:

scala> second(List(1, 2, 3))
res1: Int = 2

scala> second(Nil)
scala.MatchError: List() (of class scala.collection.immutable.Nil$)
  at .$anonfun$second(<console>:11)
  at .$anonfun$second$adapted(<console>:11)
  ... 36 elided

问题是,如果我们只有一些列表值,l,并且不知道该列表中有什么,我们不知道如果我们传递它是否会得到异常到 second 引用的函数。现在,我们可以将调用放在 try 块和 catch 任何异常中,但这很冗长而且不是很好的 函数式编程风格 。理想情况下,我们想知道是否可以先调用该函数以避免异常。不幸的是,无法从 Function1 实例中判断:

scala> second.isDefinedAt(Nil)
<console>:13: error: value isDefinedAt is not a member of List[Int] => Int
       second.isDefinedAt(Nil)
              ^

我们需要声明 second 类型 PartialFunction[List[Int], Int] 如下:

scala> val second: PartialFunction[List[Int], Int] = {
     |   case x :: y :: _ => y
     | }
second: PartialFunction[List[Int],Int] = <function1>

(顺便说一句,请注意您在问题中对这段代码有错字——上面是应该如何定义的。)

现在我们没有任何警告!我们已经告诉编译器这是一个 PartialFunction 实例,所以编译器知道它对于某些参数是未定义的,所以警告是多余的。我们现在可以验证这个事实:

scala> second.isInstanceOf[PartialFunction[List[Int], Int]]
res6: Boolean = true

我们现在还可以验证它是否为特定值定义:

scala> second.isDefinedAt(Nil)
res7: Boolean = false

scala> second.isDefinedAt(List(1, 2))
res9: Boolean = true

等等。 (书上描述的 Scala 编译器能够为我们实现这个神奇的 isDefinedAt 功能。)

那么,这是否意味着我们现在应该编写如下代码:

def getSecondValue(l: List[Int]): Option[Int] = {

  // Check if second is defined for this argument. If so, call it and wrap in Some.
  if(second.isDefinedAt(l)) Some(second(l))

  // Otherwise, we do not have a second value.
  else None
}

好吧,这也有点冗长。幸运的是,一旦 second 是一个 PartialFunction 实例,我们就可以将上面的代码重写为:

def getSecondValue(l: List[Int]): Option[Int] = second.lift(l)

lift 方法将部分函数转换为 完整函数,return 为每个参数定义了一个值:如果 [=25] 的参数=] 被定义,那么我们得到一个 Some(value);否则,我们得到 None.

随着您对 函数式编程 的熟悉,您会发现部分函数和 PartialFunction 的概念更有用。如果您现在还没有 得到它,请不要担心;一切都会明朗。

部分函数是一种函数,它不会为它可以给出的每个可能的输入值提供答案。它只为可能数据的一个子集提供答案,并定义它可以处理的数据。在 Scala 中,还可以查询部分函数以确定它是否可以处理特定值。 举个简单的例子,想象一个将一个数字除以另一个数字的普通函数:

val divide = (x: Int) => 42 / x

按照定义,当输入参数为零时,此函数会爆炸:

scala> divide(0)
java.lang.ArithmeticException: / by zero

尽管您可以通过捕获并抛出异常来处理这种特殊情况,但 Scala 允许您将除法函数定义为 PartialFunction。这样做时,您还明确声明函数是在输入参数不为零时定义的:

val divide = new PartialFunction[Int, Int] {
def apply(x: Int) = 42 / x
def isDefinedAt(x: Int) = x != 0
}

https://alvinalexander.com/scala/how-to-define-use-partial-functions-in-scala-syntax-examples

以上可以参考link。