Scala柯里化及其应用实践
1. 介绍
在Scala中方法和函数有细微的差别,通常编译器会自动完成方法到函数的转换。如果想了解Scala方法和函数的具体区别,请参考【】
2. Scala中柯里化的形式
Scala中柯里化方法的定义形式和普通方法类似,区别在于柯里化方法拥有多组参数列表,每组参数用圆括号括起来,例如:
def multiply(x: Int)(y: Int): Int = x * y
multiply方法拥有两组参数,分别是(x: Int)和(y: Int)。
multiply方法对应的柯里化函数类型是:
Int => Int => Int
柯里化函数的类型声明是右结合的,即上面的类型等价于:
Int => (Int => Int)
表明该函数若只接受一个Int参数,则返回一个Int => Int类型的函数,这也和柯里化的过程相吻合。
3. 探究柯里化函数
我们仍以上面定义的multiply方法为例探索柯里化的一些细节:
def multiply(x: Int)(y: Int): Int = x * y
上面定义了一个柯里化方法,在Scala中可以直接操纵函数,但是不能直接操纵方法,所以在使用柯里化方法前,需要将方法转换成柯里化函数。最简单的方式是使用编译器提供的语法糖:
val f = multiply _
返回的函数类型是:
Int => Int => Int
使用Scala中的部分应用函数(partially applied function)技巧也可以实现转换,但是请注意转后后得到的并不是柯里化函数,而是一个接受两个(而不是两组)参数的普通函数:
val f = multiply(_: Int)(_: Int)
转后后得到的类型为:
(Int, Int) => Int
其实就是一个接受两个Int型参数的普通函数类型。
先传入第1个参数:
val f1 = f(1)
返回类型为:
Int => Int
即一个接受一个Int参数返回Int类型的函数。继续传入第2个参数:
val f2 = f1(2)
返回类型为:
Int
两组参数都已经传入,返回一个Int类型结果。
4. 柯里化(currying)函数和部分应用函数(partial applied)的区别
下面代码定义一个普通方法multiply1
和一个currying方法multiply2
,并将其转换相应的函数类型:
def multiply1(x: Int, y:Int, z:Int) = x * y * z
val partialAppliedMultiply = multiply1 _
//类型:(x: Int, y: Int, z: Int) => Int
def multiply2(x: Int)(y: Int)(z: Int) = x * y * z
val curryingMultiply = multiply2 _
//类型:Int => (Int => (Int => Int))
在调用时,currying Multiply可以依次传入各个参数,而partialAppliedMultiply在传入部分参数时,必须显示指定剩余参数的占位符:
val curryingMultiply1 = curryingMultiply(1)
//类型:Int => (Int => Int)
val partialAppliedMultiply1 = partialAppliedMultiply(1, _:Int, _: Int)
//类型:(Int, Int) => Int
另外,curryingMultiply1的类型仍然是currying类型,而partialAppliedMultiply1的类型仍然是普通函数类型。
5. 应用:控制抽象(Control Abstraction)
5.1 控制抽象介绍
对于一些通用的操作可以实现成控制抽象,例如像文件打开、关闭操作。实现成控制抽象的好处是,可以在使用的时候,看起来更像是语言级别提供的功能。
5.2 抽象控制的实现基础
5.2.1 无参函数
无参函数的类型是() => T
,在使用时为了简化可以省略(),例如:
def runInThread(block: => Unit){
new Thread {
override def run() { block }
}.start()
}
这样定义之后,在使用的时候就可以省略() =>,
runInThread{
println("Hi")
}
5.2.2 使用{}替代()
如果方法只有一个参数,则可以使用{}替代(),例如:
runInThread{
println("Hi")
}
5.2.3 传名参数(by-name parameter)
与传名参数相对的是传值参数。传值参数在函数调用之前表达式会被求值,例如Int,Long等数值参数类型;传名参数在函数调用前表达式不会被求值,而是会被包裹成一个匿名函数作为函数参数传递下去,例如高阶函数的函数参数就是传名参数。
5.3 控制抽象示例
withPrintWriter是一个柯里化方法,它接受两组参数,第1组参数是待操作的文件资源,第2组参数是操作文件资源的函数:
def withPrintWriter(file: File)(op: PrintWriter => Unit) {
val writer = new PrintWriter(file)
try {
op(writer)
} finally {
writer.close()
}
}
用法如下:
withPrintWriter(new File("date.txt")) {
writer => writer.println(new java.util.Date)
}
withPrintWriter确保文件资源在被使用之后一定会被关闭,并且在使用的时候,看起来更像是语言内置的关键字函数。
推荐阅读
(点击标题可跳转阅读)
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
扫码二维码
获取更多精彩
长按图片关注
点个
在看
你最好看