2

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

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

因此,如果您将三元素列表而不是空列表传递给上述函数,则上述函数将成功。

second(List(5,6,7))

有效但无效

second(List())

上面会抛出一个MatchError: List

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

如果您想检查是否定义了偏函数,您必须首先告诉编译器您知道您正在使用偏函数。

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

List[Int] => Int 类型包括从整数列表到整数的所有函数,无论这些函数是否是部分函数。仅包括从整数列表到整数的部分函数的类型写为 PartialFunction[List[Int], Int].

所以上面的函数返回了一个 List[Int] => Int 类型的函数,我看到了,但是为什么我们需要把这个函数改成 typePartialFunction[List[Int], Int]呢?

这是重新定义的函数:

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

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

4

2 回答 2

4

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

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

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

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 = $$Lambda$3181/1894473818@27492c62

那么我们刚刚做了什么?我们定义second为一个函数的引用,该函数接受一个List[Int]参数并返回一个Int(列表中的第二个值)。

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

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

顺便说一句,typeList[Int] => Int是一个简写形式scala.Function1[List[Int], Int],因此seconds type 是该类型的一个实例:

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$1(<console>:11)
  at .$anonfun$second$1$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方法将一个部分函数转换为一个完整函数,该函数为每个参数返回一个定义的值:如果second定义了参数 to,那么我们得到一个Some(value); 否则,我们得到None.

您会发现偏函数的概念,并且随着您对函数式编程PartialFunction越来越熟悉,它会变得更加有用。如果您现在不明白,请不要担心;一切都会变得清晰。

于 2018-05-17T04:09:32.103 回答
0

偏函数是一个函数,它不能为它可以给出的每个可能的输入值提供答案。它只为可能数据的子集提供答案,并定义了它可以处理的数据。在 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

你可以参考上面的链接。

于 2018-05-17T04:13:50.323 回答