September 24, 2012

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的语法。当然事实上我们并没有扩展,只是改了一个面貌,但不得不说这颗语法糖真的很甜。