curring的概念將函數式編程的概念和預設參數以及可變參數結合在一起.一個帶n個參數,curried的函數固化第一個參數為固定參數,並返回另一個帶n-1個參數的函數對象,分別類似於LISP的原始函數car和cdr的行為。currying能泛化為偏函數應用(partial function application, PFA),p 這種函數將任意數量(順序)的參數的函數轉化為另一個帶剩餘參數的函數對象。
最早期的curry函數有點多態的意味,就是根據函數參數在內部選用分支:
//http://www.openlaszlo.org/pipermail/laszlo-user/2005-March/000350.html// ★★On 8 Mar 2005, at 00:06, Steve Albin wrote: function add(a, b) { if (arguments.length < 1) { return add; } else if (arguments.length < 2) { return function(c) { return a + c } } else { return a + b; } } var myadd = add( 2 ); var total = myadd(3);
日本的一個先行者可能在未搞清arguments也能用Array的原生方法轉換為數組的時候,用非常複雜的正則與eval搞出一個更接近現代currying意味的函數。
function curry(fun) { if (typeof fun != 'function') { throw new Error("The argument must be a function."); } if (fun.arity == 0) { throw new Error("The function must have more than one argument."); } var funText = fun.toString(); var args = /function .*\((.*)\)(.*)/.exec(funText)[1].split(', '); var firstArg = args.shift(); var restArgs = args.join(', '); var body = funText.replace(/function .*\(.*\) /, ""); var curriedText = "function (" + firstArg + ") {" + "return function (" + restArgs + ")" + body + "}"; eval("var curried =" + curriedText); return curried; }
<br /> function curry(fun) {<br /> if (typeof fun != 'function') {<br /> throw new Error("The argument must be a function.");<br /> }<br /> if (fun.arity == 0) {<br /> throw new Error("The function must have more than one argument.");<br /> }</p><p> var funText = fun.toString();<br /> var args = /function .*\((.*)\)(.*)/.exec(funText)[1].split(', ');<br /> var firstArg = args.shift();<br /> var restArgs = args.join(', ');<br /> var body = funText.replace(/function .*\(.*\) /, "");</p><p> var curriedText =<br /> "function (" + firstArg + ") {" +<br /> "return function (" + restArgs + ")" + body +<br /> "}";</p><p> eval("var curried =" + curriedText);<br /> return curried;<br /> }</p><p> function sum(x, y) {<br /> return x + y;<br /> }<br /> function mean3(a, b, c) {<br /> return (a + b + c)/3;<br /> }</p><p> var a = curry(sum)(10)(15)<br /> alert(a)//25<br /> var b = curry(mean3)(10)(20, 30);<br /> alert(b)//20<br /> var c = curry(curry(sum))(10)()(20);<br /> alert(c);<br /> var d = curry(curry(mean3)(10))(20)(30);<br /> alert(d);<br />
運行代碼
接著是閉包的流行,與數群組轉換arguments的技術的發現,現代currying函數終於粉墨登場,就好像15~17世紀大航海時代的地理大發現,javascript的世界突然間開闊了許多。
//一個簡單的現代currying函數function curry (fn, scope) { var scope = scope || window; var args = []; for (var i=2, len = arguments.length; i 一般的currying函數只有兩重,執行情況如下,第一次執行參數不足返回內建函式,第二次執行才最終完成。不過針對這參數,我們還是可以做一些文章。看如下函數:
function sum(){ var result=0; for(var i=0, n=arguments.length; i<n; i++){ result += arguments[i]; } return result;}alert(sum(1,2,3,4,5)); // 15
這就沒有所謂的參數不足問題,傳入一個參數,它也計算。但不傳入參數呢?無錯,區別在於有沒有參數。我們可以讓它不斷執行自身,如果參數存在的情況下。最後在沒有參數的情況下,一次過執行。換言之,前面的步驟是用於儲存參數。
var sum2= curry(sum); sum2= sum2(1)(2)(3)(4)(5);sum2(); // 15
比起一般的currying函數,這有點難度。具體看註解:
var curry= function(fn){//原函數的參數為函數 return function(args){//內建函式的參數為數組,由於立即執行,因此直接到第三重去 //args是相對於第三重內建函式可是全域變數 var self= arguments.callee;//把自身儲存起來(就是那個數組為參數的第二重函數) return function(){ //這才是第二次調用的函數 if(arguments.length){//如果還有要添加的參數 [].push.apply(args,arguments);//apply把當前傳入的所有參數放進args中 return self(args); }else{ return fn.apply(this,args);//apply的第二參數為數組 } } }([]); };
<br /> function sum(){<br /> var result=0;<br /> for(var i=0, n=arguments.length; i<n i result arguments return var curry="function(fn){//原函數的參數為函數" function self="arguments.callee;//把自身儲存起來" if else fn.apply sum2="curry(sum);" alert><p><button type="button" title="runcode1" class="runcode direct">運行代碼</button></p><p>或者每次傳入多個參數:</p><p><textarea id="runcode2" style="width:80%" rows="10"><br /> function sum(){<br /> var result=0;<br /> for(var i=0, n=arguments.length; i<n i result arguments return var curry="function(fn){//原函數的參數為函數" function self="arguments.callee;//把自身儲存起來" if else fn.apply sum2="curry(sum);" alert><p><button type="button" title="runcode2" class="runcode direct">運行代碼</button></p><p>但上面的函數有不足之處,最後怎麼也要放個括弧,我們想只要參數足夠就返回結果,多出的參數忽略。改進如下:</p><pre class="brush:javascript;gutter:false;toolbar:false"> function curry(f) { if (f.length == 0) return f; function iterate(args) { if (args.length <= f.length) return f.apply(null, args); return function () { return iterate(args.concat(Array.prototype.slice.call(arguments))); }; } return iterate([]); }</pre><p><textarea id="runcode3" style="width:80%" rows="10"><br /> function curry(f) {<br /> if (f.length == 0) return f;<br /> function iterate(args) {<br /> if (args.length >= f.length)<br /> return f.apply(null, args);<br /> return function () {<br /> return iterate(args.concat(Array.prototype.slice.call(arguments)));<br /> };<br /> }<br /> return iterate([]);<br /> }<br /> function mean3(a, b, c) { return (a + b + c) / 3; }</p><p> var curriedMean3 = curry(mean3);<br /> alert(curriedMean3(1)(2, 3)); // => 2<br /> alert(curriedMean3(1)(2)(3));//空括弧無效<br /> alert(curriedMean3()(1)()(2)()(3)); // => 2<br /> alert(curriedMean3(1, 2)(3, 4)); // => 2 (第四個參數無效)<br />
運行代碼