Scala 函数式特征
1. 函数类型
函数类型是函数式语言的特征之一;
其原因在于,函数是语言中的一等公民,可以作为变量,而变量是具有类型的。
Scala 的函数类型定义如下:
f: Int, Int => Int
使用箭头将参数类型和返回值类型相间隔;
上面的例子表示函数 f
接受两个 Int
参数,返回值类型为 Int
2. 高阶函数
高阶函数指的是接受 函数作为参数 的函数,它的参数是函数类型。
Scala 中的高阶函数如下:
def sum(f: Int => Int, a: Int, b: Int) =
if(a > b) 0
else f(a) + sum(a + 1, b)
其中,f
是函数类型的参数,它接受一个 Int
作为参数,返回值是一个 Int
;
上面的例子如下数学公式的求法:
3. 匿名函数(函数字面量, lambda)
作为语言的基本类型,如字符串,我们可以使用字面量表示它,如:
val s = "abc"
println(s)
上面可以直接写成
println("abc")
在 Scala 中,函数也具有这种特性,我们可以直接定义一个函数字面量:
val f = (x: Int) => x * x
sum(f, 1, 3)
如上,f
是一个函数,具有参数 x
,返回 x
的平方
也可以将字面量直接传入
sum(x => x * x, 1, 3)
大部分情况都不需要显式指定参数的类型,编译器会进行自动推断;
同时,无法在函数字面量中显式指定函数的返回值类型
只能通过定义函数变量的类型来进行显示指定
实际上,Scala 中的匿名函数就是其他语言中的 lambda 表达式;
就函数式上来说,Scala 提供了一种更轻便的语法
4. 柯里化
4.1 定义
柯里化是函数式范式的一个特有现象;
它指的是,一个函数,通过接受部分参数,可以返回接受剩余参数的 嵌套函数;
事实上,对于一个函数
当 时,以下的写法和上面是等价的:
所以,我们可以通过编写嵌套的接受部分参数的函数,并返回它,来达到柯里化的目的;
实际上,这个过程就叫做柯里化。
\begin{align} f(arg_1)(arg_2)\ldots(arg_n) \\ &= arg_1 \Rightarrow \{f(arg_2)\ldots(arg_n)\} \\ &= arg_1 \Rightarrow \{arg_2 \Rightarrow \{f(arg_3)\ldots(arg_n)\}\} \\ &= \cdots \\ &= arg_1 \Rightarrow arg_2 \Rightarrow arg_3 \Rightarrow \ldots \Rightarrow f \end{align}
4.2 显式柯里化
sum
函数可以使用如下的方法进行重写:
def sum(f: Int => Int): (Int, Int) => Int = {
def sumF(a: Int, b: Int) = {
if(a > b) 0
else f(a) + sumF(a + 1, b)
}
sumF
}
上面的写法被称作 显式柯里化,就是将一个接受多个参数的函数通过显式编写一个内部的嵌套函数,并返回这个函数来达到柯里化。
在调用时,我们可以直接如下调用:
sum(x => x * x) (1, 10) // 1^2 + 2^2 + ... + 10^2
第一个括号,调用了外部函数,返回值是内部的 sumF
函数;
这使得我们可以 继续使用括号 进行 sumF
的调用
4.3 隐式柯里化
许多函数式编程语言都提供柯里化的语法糖,这被称作 隐式柯里化;
Scala 也提供了这样的语法糖:
def sum(f: Int => Int)(a: Int, b: Int) =
if (a > b) 0
else f(a) + sum(f)(a + 1, b)
通过使用两个括号,就可以直接定义最内部的函数体,而不需要再定义一个内部的嵌套函数;
这可以让我们像进行柯里化函数调用一样,定义柯里化函数
4.4 柯里化的目的
柯里化相比我们定义一个多参数函数来说,要稍显复杂;
那么为什么不直接定义一个多参数函数呢?
实际上,使用柯里化的目的在于可以动态确定参数;
当函数的某些参数不确定时,我们可以先保存一个存根;
剩余的参数确定之后,可以通过存根直接调用剩下的参数。
柯里化的另一个用处类似建造者模式(Builder Pattern),可以通过柯里化来减少参数和函数重载的爆炸。
5. 部分应用(partially application)
部分应用指的是, 固定 函数的某些参数,可以获取一个接受剩下参数的函数;
有点类似于在运行时给予函数默认值。
Scala 的部分应用写法如下:
def add(a: Int, b: Int, c: Int) = a + b + c
def addA5 = add(5, _:Int, _:Int)
addA5(2, 3) // 5 + 2 + 3
可以看到,我们通过将参数 a
的值固定为 5
得到了一个新的函数;
它接受 b
和 c
,返回 5 + b + c
6. 柯里化和部分应用的区别
这两个概念经常被混淆,但是实际上有着一些差别:
- 柯里化指的是将多参数函数 分解为 多个单参数(组)函数的特性
- 部分应用指的是通过 固定 某个参数,得到接受剩余参数函数的特性
虽然它们调用的效果都是返回一个函数,但是,两者一次调用返回的函数具有显著的不同:
-
柯里化返回的函数只接受一个参数(组)
由于返回的是层层嵌套的函数,所以会出现函数的连续调用
add(1)(1)(1)(1)(1)
中,
对于一个(1)
,返回的函数是接受另一个1
,同时将剩下的内部嵌套闭包返回 -
部分应用返回的函数可以接受多个参数
相比柯里化,部分应用返回的函数可以直接接受多个参数,如
add_1(1,1,1,1)
固定了第一个1
之后,剩下的1
可以直接传入,而不需要连续调用
柯里化通过将函数分解嵌套来减少函数的参数;
函数的部分应用通过给予参数默认值来减少函数的参数。
柯里化函数的调用是函数的连续调用,而函数的部分应用是函数的一次调用。