用函數處理函數,來擴充函數原本的功能
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的方式呼叫,參數個數超過函數定義的參數個數時,就會出問題。另外,也不能接受不定參數的函數,因為需要用參數個數當作一個終止條件。也許以後再想比較好的方法來做。