Odersky书中的部分功能解释

时间:2018-05-17 02:06:08

标签: scala partial-functions

在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,我看到了,但为什么我们需要更改此函数以键入PartialFunction[List[Int], Int]

这是重新定义的功能:

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

我真的不明白。有什么好处?为什么我们要检查是否定义了部分函数?这甚至意味着什么?

2 个答案:

答案 0 :(得分:4)

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

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

函数式编程的一个关键原则是引用透明度 RT ),这意味着我们应该能够替换一个表达式(例如具有该表达式值的函数调用),而不改变程序的含义。 (有关此主题的更多信息,我强烈建议您阅读Chiusano和Bjarnason的Functional Programming in 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

顺便提一下,List[Int] => Int类型是scala.Function1[List[Int], Int]的简写,因此second类型是该类型的实例:

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
}

嗯,这也有点啰嗦。幸运的是,一旦secondPartialFunction个实例,我们就可以将上面重写为:

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

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

当你更熟悉函数式编程时,你会发现部分函数的概念,PartialFunction更有用。如果你现在没有得到它,请不要担心;一切都会变得清晰。

答案 1 :(得分: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

您可以参考以上链接。