分享

spark开发基础之Scala偏函数和偏应用函数区别

pig2 发表于 2016-12-3 14:44:10 [显示全部楼层] 只看大图 回帖奖励 阅读模式 关闭右栏 1 9419
问题导读

1.什么是偏函数?
2.什么是偏应用函数?
3.偏函数的格式是什么?
4.偏函数参数有几个?表示什么含义?







1.什么是偏函数
我们经常遇到 PartialFunction[-A, +B] ,这种带有中括号的函数,在传统中很少见。那么它到底是什么意思。其实它是Scala中的偏函数。
那么什么是偏函数?
1.偏函数是指仅定义了输入参数的子集的函数
2.我们再来看看偏函数的格式
rait PartialFunction[-A, +B] extends (A) ⇒ B
其中A是传入参数,B是返回值。

3.偏函数数只能有一个参数。
这样我们就整体认识了偏函数。

2.偏函数详解
详细我们来看:
如下图:


偏函数.png


这个图显示的一个偏函数:f : X -> Y,该函数仅定义了输入参数X的子集1和3,没有包含2。

在Scala中的偏函数是通过特质PartialFunction[-A, +B]来定义的,查看PatialFunction特质的API,可看到PatialFunction定义如下:

[mw_shl_code=bash,true]trait PartialFunction[-A, +B] extends (A) ⇒ B
[/mw_shl_code]

可以看出偏函数:
1)是一个将类型A转为类型B的特质。

2)接受A类型的输入参数,返回值为B类型。

3)是一个一元函数,“-”符号作用于类型表示逆变,-A表明输入参数为A类型或A类型的父类,也就是说输入的参数应为A的子集,具有“部分”的含义。

4)函数有可能“偏”离了A定义域(Type)类型,而成为值域B, +表明可以是B或B的子类,具有“全部”的含义。

偏函数和其它函数一样,也定义了apply方法,apply方法会从匹配到的模式计算函数值。也添加了另外一个方法“def isDefinedAt(a: A):Boolean”,isDefinedAt方法决定了该方法的参数是否在给定的偏函数的定义域内,如果返回结果为true,表示在,否则不在,最好在。见下例,signal引用了一个偏函数:

[mw_shl_code=bash,true]val signal: PartialFunction[Int, Int] = {
    case x if x > 1 => 1
    case x if x < -1 => -1
}[/mw_shl_code]

这个signal所引用的函数除了0值外,对所有整数都定义了相应的操作。 调用signal(0) 会抛出异常,因此使用前最好先signal.isDefinedAt(0)判断一下。
偏函数主要用于这样一种场景:对某些值现在还无法给出具体的操作(即需求还不明朗),也有可能存在几种处理方式(视乎具体的需求),我们可以先对需求明确的部分进行定义,比如上述除了0外的所有整数域,然后根据具体情况补充对其他域的定义,比如 :

[mw_shl_code=bash,true]val composed_signal: PartialFunction[Int,Int] = signal.orElse{
  case 0 => 0
}

composed_signal(0)  // 返回 0 [/mw_shl_code]

或者对定义域进行一定的偏移(假如需求做了变更,  1 为无效的点)

[mw_shl_code=bash,true]val new_signal: Function1[Int, Int] = signal.compose{
  case x => x  - 1
}

new_signal(1)  // throw exception
new_signal(0)   // 返回 -1
new_signal(2)  // 返回 1[/mw_shl_code]

还可以用andThen将两个相关的偏函数串接起来

[mw_shl_code=bash,true]val another_signal: PartialFunction[Int, Int] = {    case 0 =>  0
    case x if x > 0 => x - 1
    case x if x < 0 => x + 1
}

val then_signal =  another_signal andThen  signal
[/mw_shl_code]


偏应用函数
偏应用函数是指在调用函数时,有意缺少部分参数的函数。
def sum(x: Int, y: Int, z: Int) = x + y + z, 当调用sum的时候,如果不提供所有的参数,或者只提供某些参数时,比如sum _ , sum(3, _: Int, _:Int), sum(_: Int, 3, 4), 这样就生成了所谓的部分应用函数。

以下为摘自网上的文章:

Scala里,当你调用函数,传入任何需要的参数,你就是在把函数应用到参数上。如,给定下列函数:
  • scala> def sum(a: Int, b: Int, c: Int) = a + b + c
  • sum: (Int,Int,Int)Int
你就可以把函数sum应用到参数1,2和3上,如下:
  • scala> sum(1, 2, 3)
  • res12: Int = 6
偏应用函数是一种表达式,你不需要提供函数需要的所有参数。代之以仅提供部分,或不提供所需参数。比如,要创建不提供任何三个所需参数的调用sum的偏应用表达式,只要在“sum”之后放一个下划线即可。然后可以把得到的函数存入变量。举例如下:
  • scala> val a = sum _
  • a: (Int, Int, Int) => Int = < function>
有了这个代码,Scala编译器以偏应用函数表达式,sum _,实例化一个带三个缺失整数参数的函数值,并把这个新的函数值的索引赋给变量a。当你把这个新函数值应用于三个参数之上时,它就转回头调用sum,并传入这三个参数:
  • scala> a(1, 2, 3)
  • res13: Int = 6
实际发生的事情是这样的:名为a的变量指向一个函数值对象。这个函数值是由Scala编译器依照偏应用函数表达式sum _,自动产生的类的一个实例。编译器产生的类有一个apply方法带三个参数。产生的类扩展了特质Function3,定义了三个参数的apply方法。之所以带三个参数是因为sum _表达式缺少的参数数量为三。Scala编译器把表达式a(1,2,3)翻译成对函数值的apply方法的调用,传入三个参数1,2,3。因此a(1,2,3)是下列代码的短格式:
  • scala> a.apply(1, 2, 3)
  • 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 _的例子里,它没有应用于任何参数。不过还可以通过提供某些但不是全部需要的参数表达一个偏应用函数。举例如下:
  • scala> val b = sum(1, _: Int, 3)
  • b: (Int) => Int = < function>
这个例子里,你提供了第一个和最后一个参数给sum,但中间参数缺失。因为仅有一个参数缺失,Scala编译器会产生一个新的函数类,其apply方法带一个参数。在使用一个参数调用的时候,这个产生的函数的apply方法调用sum,传入1,传递给函数的参数,还有3。如下:
  • scala> b(2)
  • res15: Int = 6
这个例子里,b.apply调用了sum(1,2,3)。
  • scala> b(5)
  • 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允许你仅在需要函数类型的地方才能省略这个仅用的_。


参考
qiruiduni

文章内容:


spark开发基础之从Scala符号入门Scala
http://www.aboutyun.com/forum.php?mod=viewthread&tid=20159

spark开发基础之从关键字入门Scala
http://www.aboutyun.com/forum.php?mod=viewthread&tid=20223

更多内容:
spark开发基础之Scala快餐:开发环境Intellij IDEA 快捷键整理【收藏备查】
http://www.aboutyun.com/forum.php?mod=viewthread&tid=20380


spark开发基础之Scala详解apply方法
http://www.aboutyun.com/forum.php?mod=viewthread&tid=20423


已有(1)人评论

跳转到指定楼层
rossette 发表于 2016-12-4 12:00:46
感觉偏函数和函数指针很像,不过函数指针只能指向函数,不可以指定部分参数值
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

推荐上一条 /2 下一条