Scala: 偏函数(PartialFunction) && 偏应用函数(Partial Applied Function)

来源:互联网 发布:矩阵A2怎么算 编辑:程序博客网 时间:2024/05/13 10:07

偏函数(

  • 偏函数是只对函数定义域的一个子集进行定义的函数。 scala中用scala.PartialFunction[-T, +S]类来表示

Scala提供了定义偏函数(PartialFunction)的语法快捷:

 val pf: PartialFunction[Int, String] = {   case i if i%2 == 0 => "even" }

它们也可能由 orElse 组成:

 val tf: (Int => String) = pf orElse { case _ => "odd"} tf(1) == "odd" tf(2) == "even"

偏函数出现在很多场景,并被有效的编码为 PartialFunction,例如 方法参数:

 trait Publisher[T] {   def subscribe(f: PartialFunction[T, Unit]) } val publisher: Publisher[Int] = .. publisher.subscribe {   case i if isPrime(i) => println("found prime", i)   case i if i%2 == 0 => count += 2   /* ignore the rest */ }

或在返回一个Option的情况下:

 // Attempt to classify the the throwable for logging. type Classifier = Throwable => Option[java.util.logging.Level]

可以更好的用PartialFunction表达

 type Classifier = PartialFunction[Throwable, java.util.Logging.Level]

因为它提供了更好的可组合性:

 val classifier1: Classifier val classifier2: Classifier val classifier = classifier1 orElse classifier2 orElse { _ => java.util.Logging.Level.FINEST }偏应用函数(
  • 偏应用函数(Partial Applied Function)是缺少部分参数的函数,是一个逻辑上概念

Scala里,当你调用函数,传入任何需要的参数,你就是在把函数应用到参数上。如,给定下列函数:

 

  1. scala> def sum(a: Int, b: Int, c: Int) = a + b + c  
  2. sum: (Int,Int,Int)Int  
你就可以把函数sum应用到参数1,2和3上,如下:


  1. scala> sum(123)  
  2. res12: Int = 6 
偏应用函数是一种表达式,你不需要提供函数需要的所有参数。代之以仅提供部分,或不提供所需参数。比如,要创建不提供任何三个所需参数的调用sum的偏应用表达式,只要在“sum”之后放一个下划线即可。然后可以把得到的函数存入变量。举例如下:


  1. scala> val a = sum _  
  2. a: (Int, Int, Int) => Int = < function>  
有了这个代码,Scala编译器以偏应用函数表达式,sum _,实例化一个带三个缺失整数参数的函数值,并把这个新的函数值的索引赋给变量a。当你把这个新函数值应用于三个参数之上时,它就转回头调用sum,并传入这三个参数:


  1. scala> a(123)  
  2. res13: Int = 6 
实际发生的事情是这样的:名为a的变量指向一个函数值对象。这个函数值是由Scala编译器依照偏应用函数表达式sum _,自动产生的类的一个实例。编译器产生的类有一个apply方法带三个参数。产生的类扩展了特质Function3,定义了三个参数的apply方法。之所以带三个参数是因为sum _表达式缺少的参数数量为三。Scala编译器把表达式a(1,2,3)翻译成对函数值的apply方法的调用,传入三个参数1,2,3。因此a(1,2,3)是下列代码的短格式:

 

  1. scala> a.apply(123)  
  2. res14: Int = 6 
Scala编译器根据表达式sum _自动产生的类里的apply方法,简单地把这三个缺失的参数前转到sum,并返回结果。本例中apply调用了sum(1,2,3),并返回sum返回的,6。

 

这种一个下划线代表全部参数列表的表达式的另一种用途,就是把它当作转换def为函数值的方式。例如,如果你有一个本地函数,如sum(a: Int, b: Int, c: Int): Int,你可以把它“包装”在apply方法具有同样的参数列表和结果类型的函数值中。当你把这个函数值应用到某些参数上时,它依次把sum应用到同样的参数,并返回结果。尽管不能把方法或嵌套函数赋值给变量,或当作参数传递给其它方法,但是如果你把方法或嵌套函数通过在名称后面加一个下划线的方式包装在函数值中,就可以做到了。

现在,尽管sum _确实是一个偏应用函数,或许对你来说为什么这么称呼并不是很明显。这个名字源自于函数未被应用于它所有的参数。在sum _的例子里,它没有应用于任何参数。不过还可以通过提供某些但不是全部需要的参数表达一个偏应用函数。举例如下:

 

  1. scala> val b = sum(1, _: Int, 3)  
  2. b: (Int) => Int = < function> 
这个例子里,你提供了第一个和最后一个参数给sum,但中间参数缺失。因为仅有一个参数缺失,Scala编译器会产生一个新的函数类,其apply方法带一个参数。在使用一个参数调用的时候,这个产生的函数的apply方法调用sum,传入1,传递给函数的参数,还有3。如下:

 

 

  1. scala> b(2)  
  2. res15: Int = 6 
这个例子里,b.apply调用了sum(1,2,3)。

 

 

  1. scala> b(5)  
  2. res16: Int = 9 
这个例子里,b.apply调用了sum(1,5,3)。为什么要使用尾下划线?

 

Scala的偏应用函数语法凸显了Scala与经典函数式语言如Haskell或ML之间,设计折中的差异。在经典函数式语言中,偏应用函数被当作普通的例子。更进一步,这些语言拥有非常严格的静态类型系统能够暴露出你在偏应用中可能犯的所有错误。Scala与指令式语言如Java关系近得多,在这些语言中没有应用所有参数的方法会被认为是错误的。进一步说,子类型推断的面向对象的传统和全局的根类型接受一些被经典函数式语言认为是错误的程序。

举例来说,如果你误以为List的drop(n: Int)方法如tail(),那么你会忘记你需要传递给drop一个数字。你或许会写,“println(drop)”。如果Scala采用偏应用函数在哪儿都OK的经典函数式传统,这个代码就将通过类型检查。然而,你会惊奇地发现这个println语句打印的输出将总是< function>!可能发生的事情是表达式drop将被看作是函数对象。因为println可以带任何类型对象,这个代码可以编译通过,但产生出乎意料的结果。

为了避免这样的情况,Scala需要你指定显示省略的函数参数,尽管标志简单到仅用一个‘_’。Scala允许你仅在需要函数类型的地方才能省略这个仅用的_。

Reference: 1. http://zh.wikipedia.org/wiki/%E5%87%BD%E6%95%B02. http://twitter.github.io/effectivescala/index-cn.html#函数式编程-偏函数3. Martin Odersky / Lex Spoon / Bill Venners.《Programming in Scala》