本文链接://image109.360doc.com/DownloadImg/2019/08/2711/169449177_2_20190827111205691
偏函数
偏函数是指仅定义了输入参数的子集的函数,如下图:
本文链接://image109.360doc.com/DownloadImg/2019/08/2711/169449177_2_20190827111205691 偏函数 偏函数是指仅定义了输入参数的子集的函数,如下图:
这个图显示的一个偏函数:f : X -> Y,该函数仅定义了输入参数X的子集1和3,没有包含2。
在Scala中的偏函数是通过特质PartialFunction[-A, +B]来定义的,查看PatialFunction特质的API,可看到PatialFunction定义如下:
trait PartialFunction[-A, +B] extends (A) ⇒ B
可以看出偏函数:
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引用了一个偏函数:
这个signal所引用的函数除了0值外,对所有整数都定义了相应的操作。 调用signal(0) 会抛出异常,因此使用前最好先signal.isDefinedAt(0)判断一下。
偏函数主要用于这样一种场景:对某些值现在还无法给出具体的操作(即需求还不明朗),也有可能存在几种处理方式(视乎具体的需求),我们可以先对需求明确的部分进行定义,比如上述除了0外的所有整数域,然后根据具体情况补充对其他域的定义,比如 :
或者对定义域进行一定的偏移(假如需求做了变更, 1 为无效的点)
还可以用andThen将两个相关的偏函数串接起来
这里的then_signal 剔除了-1, 0, 1三个点的定义。
偏应用函数
偏应用函数是指在调用函数时,有意缺少部分参数的函数。
以下为摘自网上的文章:
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允许你仅在需要函数类型的地方才能省略这个仅用的_。
联系客服