《Scala学习手册》中文版将first-class翻译成首类,在这里不过多纠结这个翻译。实际上first-class表明了函数在函数式编程中的地位,它属于第一公民!
函数类型、函数值
在Scala里,函数本身也可以当作实例传入传出,那么可以用如下形式表示函数的类型:([<type>, ...]) => <type>
;
在def的函数标识符double(即函数名)赋给val变量时,需要有显示的类型,例如:
|
|
没有显示的类型会报错,在REPL不能单独输入double这个函数的标识符;
如果不想有显示的类型,可以使用通配符_
:
|
|
函数字面量(匿名函数)
一个函数可以让某个函数类型的值作为输入参数或返回值,那么这个函数就是高阶函数;
传递函数给高阶函数,可以采用函数字面量内联定义;
这里的函数字面量对应的是Java 8中的Lambda表达式;
接下来看看函数字面量的表示形式:
|
|
可以看到一个匿名函数,他有输入(即(x: Int)),有返回(即x * 2),但是就是没有函数名;
在上一篇文章中,我说“=”相当于Java 8的“->”,那是根据“=”之于后面出现的大括号的作用而言的,在这里“=>”也相当于Java 8中的“->”,这也是根据“=>”对前后承接的内容产生的作用而言的;
下面看看函数字面量对函数值的修改:
|
|
可以看出,此时给maximize赋值不需要添加显示类型
;
占位符语法
使用条件书中列了两条:
- 外置位已经指定了类型:只有这样,当你使用
_
,函数能匹配出其类型; - 参数不重复使用:重复使用你就不知道哪个
_
代表哪个参数了;
这里用一个简单的例子说明:
|
|
可见,占位符能对匿名函数
进行很好的缩写;
部分应用函数和柯里化
这一小节主要讲用单参数函数“解构”多参数函数,柯里化可以使获得部分应用函数更加简洁;
普通方式
|
|
普通方式是用内嵌函数实现的,外围函数接收一个内嵌函数的返回;
通配符赋值的方式
|
|
此时需要显示指定类型;
更正后:
|
|
用通配符_
替代参数,当继续调用函数时,不需要再输入3;
柯里化方式
柯里化方式就是第一篇末尾提到的参数组:
|
|
可以发现柯里化比起前面两种更清晰和简洁;
传名参数
传名参数类似Java 8里的行为参数化,将函数作为参数传入时并不进行计算,而在方法体内,每调用一次执行一次。接下来会首先采用传名参数的方法来使用函数调用,之后会采用类似Java 8中Lambda表达式实现的方式给出另一个效果相同的版本;
传名参数的方法
首先定义被传入参数的函数doubles:
|
|
现在它接受一个Int值,也接受一个返回Int值的函数,在定义返回Int值的函数f:
|
|
测试输出结果:
|
|
f(8)的计算延迟到方法体中进行,每次调用都会执行一次f(8);
类似Java 8中的方法
在Scala中不采用传名参数也是可以的:
|
|
可见打印出的结果和之前采用传名参数的方法相同;
在Java 8中,doubles函数传入的x参数通常是Lambda表达式,而这里直接传入函数值;
这样做的好处其实在《Java 8实战》中有提到,比如打印日志,不必每次都执行而是视条件是否满足而定,这样会在一些场景下节约不必要的开销;
偏函数
有的资料中中文翻译的偏函数
和部分应用函数
彼此是颠倒的,这里还是按照本书中文翻译为准;
只能部分应用于输入数据的函数称为偏函数,即对有些传入的参数,函数将不能工作;
用函数字面量块调用高阶函数
实际在上一篇已经讨论过一些()
和{}
,这里先放上一些实例;
实例1:
|
|
从这个例子可以看出,Scala的匿名函数是可以通过{}
包裹后传入函数的,()
是{}
那么()
可以省略;
实例2:
|
|
从这个例子可以看出,参数组在遇到{}
同样可以省略()
;
实例3:
|
|
可以看到aca是传名函数,参数可以是函数值、函数的调用以及{}
块,这里{}
虽然可以复合语句,但是其实是将其当作传入的函数值来运用,值是最后的的表达式或者语句返回的内容;
小结
本篇算是上一篇的进阶,重要的知识点包括函数类型、匿名函数、传名函数啊等等,通过研究这些知识点,本人对Scala函数有了更深入的理解。