用函數處理函數,來擴充函數原本的功能
Thu, 23 Sep 2010 14:25:04 +0800最近在看nodejs討論群組,有人問怎樣可以讓函數只執行一次。後來想出了一個簡單的Once()函數,可以「處理」令一個函數讓他只執行一次。接下來又想到好幾個可以這樣子擴充別的函數功能的函數,所以紀錄一下。
其實做法也很簡單,就是在原本的函數外面用匿名函數包裝,讓原本的函數在適當的條件下被執行。我想到了幾個用的到的功能:
- 控制函數只執行一次
- 控制函數執行次數
- 讓函數稍後執行
- 把函數currying
- 讓函數能遞迴執行
var Sugars = { 'Once': function(f){ var active = false; return function(){ var args = []; for(var i=0; i<arguments.length; i++){ args.push(arguments[i]); } if(active) return; active = true; return f.apply(null, args); }; }, 'Times': function(f,n){ var count = n; return function(){ var args = []; for(var i=0; i<arguments.length; i++){ args.push(arguments[i]); } if(count>0){ count--; return f.apply(null, args); } }; }, 'Later': function(f,cb) { return function() { var args = []; for(var i=0; i<arguments.length; i++){ args.push(arguments[i]); } setTimeout(function(){ if(cb) return cb(f.apply(null, args)); return f.apply(null, args); },50); }; }, 'Curry': function(f) { var orig_arg_num = f.length, args = [], inner = function() { for(var i=0; i<arguments.length; i++) { args.push(arguments[i]); } if(args.length<f.length) { return inner; } if(args.length>=f.length) { return f.apply(this, args); } }; if(arguments.length>1) { for(var i=1; i<arguments.length; i++) { args.push(arguments[i]); } } return inner; }, 'Recurser': function(f) { return function(x) { return f(function() { var args = []; for(var i=0; i<arguments.length; i++) { args.push(arguments[i]); } return x(x).apply(null, args); }); }(function(x) { return f(function() { var args = []; for(var i=0; i<arguments.length; i++) { args.push(arguments[i]); } return x(x).apply(null, args); }); }); } };
除了Recurser之外,使用上應該都不困難。大致說明一下:
- Sugars.Once(f):傳給他f函數,產生的新函數,只能執行一次。
- Sugars.Times(f, n):傳給他f函數,產生的新函數,只能執行n次。
- Sugars.Later(f, cb):傳給他f函數及cb函數,產生的新函數,執行時間會稍微延後,執行結果會當作參數傳給cb函數然後執行。
- Sugars.Curry(f, a1, a2, ...):傳給他f函數及要傳給f函數的部份參數,產生的函數,可以在執行時傳給他剩下的參數。
- Sugars.Recurser(f):傳給他f函數,產生的函數可以接受值做遞迴運算,但是...函數必須符合一定的寫法格式。
以下是自己寫的簡單測試:
var f = function(){ return "executed."; }; var t1a = Sugars.Once(f); try { alert(t1a()); alert(t1a()); }catch(e){alert(e);} var t2a = Sugars.Times(f, 2); try { alert(t2a()); alert(t2a()); alert(t2a()); }catch(e){alert(e);} var t3a = Sugars.Later(f, function(m){alert(m);}); try { t3a(); }catch(e){alert(e);} var f1 = function(a,b,c,d,e) { return e-d+c-b+a; }; var t4a = Sugars.Curry(f1, 1, 2, 3); try { alert(t4a(4)(5)); }catch(e){alert(e);} var f3 = function(g) { return function(n){ if(n==0) { return 0; } else if(n==1) { return 1; } else if(n>1) { return g(n-1) + g(n-2); } }; }; var t5a = Sugars.Recurser(f3); try { alert(t5a); alert(t5a(5)); }catch(e){alert(e);}
Sugars.Recurser()的問題還很多,建議還不要測試,因為這樣比直接寫遞迴還麻煩,哈哈。因為需要把函數currying化,第一次接受的參數其實是函數自己,第二次接受的參數才是函數需要的參數。Y-Combinator有點複雜,我還沒改出可以傳給他非currying形式的函數。不過這個東西應該沒有什麼實際用處。
Sugars.Curry()在你用currying的方式呼叫,參數個數超過函數定義的參數個數時,就會出問題。另外,也不能接受不定參數的函數,因為需要用參數個數當作一個終止條件。也許以後再想比較好的方法來做。