函數柯里化currying,是函數式編程非常重要的一個標志。它的實現需要滿足以下條件,首先就是函數可以作為參數進行傳遞,然后就是函數可以作為返回值return出去。我們依靠這個特性編寫很多優雅酷炫的代碼。那我們來看一下最簡單的實現。
大家一般都是舉addSum的例子,我當然也不例外。
add = (num1)->
return (num2)->
return num1 + num2;
add3 = add(3);
add5 = add(5);
add3(5) # 返回8
add5(5) # 返回10
上述例子其實已經對柯里化的實現,有一個非常好的了解了。其實也就是“分步求值”,我們可以把第一個參數通過閉包保存起來,以供return出去的匿名函數使用。所以我們可以根據add來自定義各種各樣的新函數。
我們要使某個函數可以柯里化,難道一定要在函數創建時,就具有柯里化的特性么?假設我們的add函數,起初並不具有柯里化特性的,我們需要怎么做才能讓它柯里化呢?
add = (num1, num2)->
return num1 + num2;
curry = (fn)->
args = [].slice.call(arguments, 1);
return ()->
[].push.apply(args, arguments);
return fn.apply(this, args);
add5 = curry(add, 5)
add5(3) # 返回8
原理還是一樣的,我們通過curry函數,讓fn需要的第一次的參數通過閉包保存在args的變量里,以供匿名函數使用。最后結合第二次需要的參數,使用apply一次性導入args,完成操作。
上述我們看到的都是分兩步求值,這其實並不符合我們更豐富的實際需求。我們需要考慮如何才可以將函數柯里化變成我們需要的想分步便分步,想停止便停止呢?
首先我們需要約定一個規則,這個規則和大部分的Getter/Setter方法一樣。當函數沒有參數時,執行的是Getter,而有參數的話,則是執行“Setter”。(這個也是Javascript實現簡陋的函數重載的一種方法)
curry = (fn)->
args = [];
return ()->
if arguments.length == 0
return fn.apply(this, args);
else
[].push.apply(args, arguments);
return arguments.callee;
addSum = ()->
sum = 0;
for num in arguments
sum += num;
return sum;
currySum = curry(addSum);
currySum(1, 2, 3);
currySum(1);
currySum(1);
currySum(1);
currySum(1);
currySum(); # 返回 10
實現原理其實也很簡單,通過閉包,將每次的參數保存在args數組了。當不傳參執行Getter時,就直接通過apply函數,將數組參數導入。我們只需要在addSum函數那里處理好導入的參數數組即可。
更多的柯里化帶來的妙處,則需要你在實際使用中,細細品味。相信一旦你掌握了這個靈活可靠的方法,可以為你帶來不一樣的感受。