Javascript函數語言程式設計的一些例子[轉載]

水之原發表於2013-06-27

函數語言程式設計風格

通常來講,函數語言程式設計的謂詞(關係運算子,如大於,小於,等於的判斷等),以及運算(如加減乘數等)都會以函式的形式出現,比如:
    a > b
通常表示為:
    gt(a, b)//great than
因此,可以首先對這些常見的操作進行一些包裝,以便於我們的程式碼更具有“函式式”風格:
function abs(x){ return x>0?x:-x;}
function add(a, b){ return a+b; }
function sub(a, b){ return a-b; }
function mul(a, b){ return a*b; }
function div(a, b){ return a/b; }
function rem(a, b){ return a%b; }
function inc(x){ return x + 1; }
function dec(x){ return x - 1; }
function equal(a, b){ return a==b; }
function great(a, b){ return a>b; }
function less(a, b){ return a<b; }
function negative(x){ return x<0; }
function positive(x){ return x>0; }
function sin(x){ return Math.sin(x); }
function cos(x){ return Math.cos(x); }

如果我們之前的編碼風格是這樣:
// n*(n-1)*(n-2)*...*3*2*1
function factorial(n){
    if(n == 1){
        return 1;
    }else{
        return n * factorial(n - 1);
    }
}

在函式式風格下,就應該是這樣了:
function factorial(n){
    if(equal(n, 1)){
        return 1;
    }else{
        return mul(n, factorial(dec(n)));
    }
}

函數語言程式設計的特點當然不在於編碼風格的轉變,而是由更深層次的意義。比如,下面是另外一個版本的階乘實現:
/*
* product <- counter * product
* counter <- counter + 1
* */
function factorial(n){
    function fact_iter(product, counter, max){
        if(great(counter, max)){
            return product;
        }else{
            fact_iter(mul(counter, product), inc(counter), max);
        }
    }
    return fact_iter(1, 1, n);
}

雖然程式碼中已經沒有諸如+/-/*//之類的操作符,也沒有>,<,==,之類的謂詞,但是,這個函式仍然算不上具有函數語言程式設計風格,我們可以改進一下:
function factorial(n){
    return (function factiter(product, counter, max){
        if(great(counter, max)){
            return product;
        }else{
            return factiter(mul(counter, product), inc(counter), max);
        }
    })(1, 1, n);
}
factorial(10);

通過一個立即執行的函式 factiter,將外部的 n 傳遞進去,並立即參與計算,最終返回運算結果。

Y-結合子

提到遞迴,函式式語言中還有一個很有意思的主題,即:如果一個函式是匿名函式,能不能進行遞迴操作呢?如何可以,怎麼做?我們還是來看階乘的例子:
function factorial(x){
    return x == 0 ? 1 : x * factorial(x-1);
}

factorial 函式中,如果 x 值為 0,則返回 1,否則遞迴呼叫 factorial,引數為 x 減 1,最後當 x 等於 0 時進行規約,最終得到函式值(事實上,命令式程式語言中的遞迴的概念最早即來源於函數語言程式設計中)。現在考慮:將 factorial 定義為一個匿名函式,那麼在函式內部,在程式碼 x*factorial(x-1)的地方,這個 factorial 用什麼來替代呢?

lambda 演算的先驅們,天才的發明了一個神奇的函式,成為 Y-結合子。使用 Y-結合子,可以做到對匿名函式使用遞迴。關於 Y-結合子的發現及推導過程的討論已經超出了本部分的範圍,有興趣的讀者可以參考附錄中的資料。我們來看看這個神奇的 Y-結合子:
var Y = function(f) {
    return (function(g) {
        return g(g);
    })(function(h) {
        return function() {
            return f(h(h)).apply(null, arguments);
        };
    });
};

我們來看看如何運用 Y-結合子,依舊是階乘這個例子:
var factorial = Y(function(func){
    return function(x){
        return x == 0 ? 1 : x * func(x-1);
    }
});
factorial(10);

或者:
Y(function(func){
    return function(x){
        return x == 0 ? 1 : x * func(x-1);
    }
})(10);

不要被上邊提到的 Y-結合子的表示式嚇到,事實上,在 JavaScript 中,我們有一種簡單的方法來實現 Y-結合子:
var fact = function(x){
    return x == 0 : 1 : x * arguments.callee(x-1);
}
fact(10);

或者:
(function(x){
    return x == 0 ? 1 : x * arguments.callee(x-1);
})(10);//3628800

其中,arguments.callee 表示函式的呼叫者,因此省去了很多複雜的步驟。

其他例項

下面的程式碼則頗有些“開發智力”之功效:
//函式的不動點
function fixedPoint(fx, first){
    var tolerance = 0.00001;
    function closeEnough(x, y){return less( abs( sub(x, y) ), tolerance)};
    function Try(guess){//try 是javascript中的關鍵字,因此這個函式名為大寫
        var next = fx(guess);
        //print(next+" "+guess);
        if(closeEnough(guess, next)){
            return next;
        }else{
            return Try(next);
        }
    };
    return Try(first);
}
// 數層巢狀函式,
function sqrt(x){
    return fixedPoint(
        function(y){
            return function(a, b){ return div(add(a, b),2);}(y, div(x, y));
        },
    1.0);
}
print(sqrt(100));

fiexedPoint 求函式的不動點,而 sqrt 計算數值的平方根。這些例子來源於《計算機程式的構造和解釋》,其中列舉了大量的計算例項,不過該書使用的是 scheme 語言,在本書中,例子均被翻譯為 JavaScript。

相關文章