Scala 自定义控制结构
写了两年Python了想换换口味,正好在coursera上参加Functional Programming Principles in Scala课程,考虑到Scala那一大票很诱人的特性就学Scala吧~ //golang 我对不起你… …
话说Scala被定义为 Scalable Language ,其实解释一下不就是可以扩展自己的语法么,作为Pythoner感觉这种事情一点也不算稀奇,然而当我真的看/用到这种特性的时候的确感觉惊叹,这不是静态或动态语言的区别,这是函数式与非函数式语言的区别。
虽然Python支持函数式的风格,但其编程思想终究是指令式为主的,所以有一些函数式特性并不能被真正发挥出来。
在 Programming in Scala 的第14章讲到 断言与测试 ,其中给出一个关于测试的例子:
class ElementSuite extends FunSuite {
test("elem result should have passed width") {
val ele = elem('x', 2, 3)
assert(ele.width == 2)
}
}
这其中的test
并不是一个内建的的关键字,它是被自定义出来的,在常见的语言中,这样的功能被定义为一个函数,例如如果这段代码用javascript来写的话那么大概会长这样 /* 原想用python写可惜lambda不支持多行 */:
function element_suite(){
ele = elem('x', 2, 3);
test( 'elem result should have passed width', function(){
var ele = elem('x', 2, 3);
assert( ele.width == 2 ); // javascript并没有内建的assert,这里只是意思一下
})
}
也就是说在其他的支持函数式特性的语言中,同样的功能是可以被足够优雅的实现的,但是这个test
并没有被扩展到语法中,多少令人感觉不够酷。
Scala提供了一颗很甜的语法糖,就是如果一个方法只有一个参数,那么可以把()
替换为{}
,例如 println("Hello World")
和 println{"Hello World"}
是等价的,但是多于一个参数的方法就不能这么用了。不过我们可以定义一个匿名函数,把语句封装到函数中,再将这个函数作为参数传入。例如:
def foo( bar: () => Unit ) = {
bar()
}
foo{ () => {
println("hello world!")
printlin("hello world, again!")
}
}
现在的foo
长得有点像控制符了,但是还是不太爽,因为 () => {}
暴露了匿名函数的存在,事实上这点语法也比较多余,然而scala提供了一块很甜的语法糖: by-name parameter 。我们稍稍改写一下上面那段代码:
def foo2( bar: => Unit ) = {
bar
}
foo2{
println("hello world!")
printlin("hello world, again!")
}
这里并没有指定bar
是一个函数,而是使用了 call-by-name 的一种求值策略( Evaluation Strategy ),也就是说参数被传入后再进行求值,于是bar
被求值的过程中那两条println语句也被执行。
现在看看上面那段代码跟第一段代码中的test
还是有区别,因为事实上test
接受了两个参数,而上文说到使用{}
代替()
仅对单个参数有效,那么多个参数怎么办? 于是我们需要用到函数式编程的大杀器: currying。
currying is the technique of transforming a function that takes multiple arguments (or an n-tuple of arguments) in such a way that it can be called as a chain of functions each with a single argument – Wikipedia
Currying就是将一个带多个参数的函数转化为一系列带一个参数的函数链,用数学公式表达即是 若有函数$f(x,y)$, 令$g=f(x,\cdot)$ 那么 $g(y)$ 等价于 $f(x,y)$,显然这里的 $g(\cdot)$ 是一个二阶函数。在scala中,我们可以使用currying扩展上一段代码:
def foo3(message: String)(bar: => Unit ) = {
println(message)
bar
}
foo3("I will say hello world~ "){
println("hello world!")
printlin("hello world, again!")
}
这里foo3
是一个二阶函数,foo3("baz")
才是一个(bar: =>Unit)Unit
型的一阶函数,现在就完美的仿真了一个控制符,至少从外表看我们成功地扩展了Scala的语法。当然事实上我们并没有扩展,只是改了一个面貌,但不得不说这颗语法糖真的很甜。