六,闭包,尾递归,高阶函数,偏函数等

来源:互联网 发布:硬笔行书知乎 编辑:程序博客网 时间:2024/06/11 06:19

闭包

闭包是可以包含自由(未绑定到特定对象)变量的代码块;这些变量不是在这个代码块内或者任何全局上下文中定义的,而是在定义代码块的环境中定义(局部变量)。比如说,在函数字面量中使用定义在其外的局部变量,这就形成了一个闭包。如下代码foreach中就创建了一个闭包:

var sum = 0

         List(1,2,3,4,5).foreach(x=> sum += x)

         在scala中,闭包捕获了变量本身,而不是变量的值。变量的变化在闭包中是可见的,反过来,若闭包改变对应变量的值,在外部也是可见的。

 

尾递归

         递归调用这个动作在最后的递归函数叫做尾递归。scala编译器可以对尾递归做出重要优化,当其检测到尾递归就用新值更新函数参数,然后把它替换成一个回到函数开头的跳转。

         你可以使用开关“-g:notailcalls”关掉编译器的尾递归优化。

         别高兴太早,scala里尾递归优化的局限性很大,因为jvm指令集使实现更加先进的尾递归形式变得困难。尾递归优化限定了函数必须在最后一个操作调用本身,而不是转到某个“函数值”或什么其他的中间函数的情况。

         在scala中,你不要刻意回避使用递归,相反,你应该尽量避免使用while和var配合实现的循环。

 

高阶函数

         带有其他函数作为参数的函数称为高阶函数

 

柯里化

         柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。如下就是一个柯里化之后的函数:

                   def curriedSum(x: Int)(y: Int) = x + y

这里发生的事情是当你调用curriedSum时,实际上接连调用了两个传统函数。第一个调用的函数带单个名为x的参数,并返回第二个函数的函数值;这个被返回的函数带一个参数y,并返回最终计算结果。你可以使用部分应用函数表达式方式,来获取第一个调用返回的函数,也即第二个函数,如下:

                   val onePlus = curriedSum(3)_

 

高阶函数柯里化配合使用可以提供灵活的抽象控制,更进一步,当函数只有一个参数时,在调用时,你可以使用花括号代替小括号,scala支持这种机制,其目的是让客户程序员写出包围在花括号内的函数字面量,从而让函数调用感觉更像抽象控制,不过需要注意的是:花括号也就是块表达式,因此你可以在其中填写多个表达式,但是最后一个表达式的值作为该块表达式的值并最终成为了函数参数。如果函数有两个以上的参数,那么你可以使用柯里化的方式来实现函数。

 

传名参数

对于如下代码,myAssert带有一个函数参数,该参数变量的类型为不带函数参数的函数类型:

                   myAssert(predicate: () => Boolean) = {

         if(!predicate())

                   thrownew AssertionError

}

在使用时,我们需要使用如下的语法:

                   myAssert(() => 5 > 3)

这样很麻烦,我们可以使用如下称之为“传名参数”的语法简化之:

                   myAssert(predicate: => Boolean) = {

         if(!predicate)

                   thrownew AssertionError

}

以上代码在定义参数类型时是以“=>”开头而不是“() =>”,并在调用函数(通过函数类型的变量)时,不带“()”。现在你就可以这样使用了:

                   myAssert(5 > 3)

其中,“predicate: =>Boolean”说明predicate是函数类型,在使用时传入的是函数字面量。注意与“predicate: Boolean”的不同,后者predicate是Boolean类型的(表达式)。

 

偏函数

         偏函数部分应用函数是无关的。偏函数是只对函数定义域的一个子集进行定义的函数。scala中用scala.PartialFunction[-T,+S]来表示。偏函数主要用于这样一种场景:对某些值现在还无法给出具体的操作(即需求还不明朗),也有可能存在几种处理方式(视乎具体的需求),我们可以先对需求明确的部分进行定义,以后可以再对定义域进行修改。PartialFunction中可以使用的方法如下:

isDefinedAt:判断定义域是否包含指定的输入。

orElse:补充对其他域的定义。

compose:组合其他函数形成一个新的函数,假设有两个函数f和g,那么表达式f _ compose g _则会形成一个f(g(x))形式的新函数。你可以使用该方法对定义域进行一定的偏移。

andThen:将两个相关的偏函数串接起来,调用顺序是先调用第一个函数,然后调用第二个,假设有两个函数f和g,那么表达式f _ andThen g _则会形成一个g(f(x))形式的新函数,刚好与compose相反。

原创粉丝点击