這次來學習一下Partial Application。我們先來看看函數的介紹,在維基上有簡單的介紹:
在數學中,一個函數是描述每個輸入值對應唯一輸出值的這種對應關係,符號為 f(x)。例如,表達式 f(x)=x2表示了一個函數 f,其中每個輸入值x都與唯一輸出值x2連結。
因此,如果一個輸入值為3,那麼它所對應的輸出值為9。而g(x,y) = xy有兩個參量x和y,以積xy為值。上面描述了函數(為方便假設x,y都是int),並且給了函數的兩個例子,先換個方式來看,f(x)可以表示為:x -> y(x2),即經過f到x2的映射,寫成int -> int。
接受一個int 回傳一個int。再看g(x,y)可以表示為:x -> y -> z(xy)。即x,y經過g的映射到z,寫成 int -> int -> int。我們看g(x,y)函數,用javascript來實作一下:
function g(x,y){
return x*y;
}
很完美啊,很接近數學定義。它依次接受兩個參數,x與y。並且傳回它們兩個的乘積。但是當x是個常數,例如x=n(n是一個自然數)。那麼g(n,y)=ny。這就變成一個常數與一個變數的乘積,它接受一個參數y回傳ny,也就是y -> z(ny) 的映射,寫成 int -> int。因此,我們可以這樣來理解上面的工作,g(x,y)是接受一個參數int,並且傳回一個函數 int ->int 。這個傳回的函數只接受一個int 並且回傳一個int。來用javascript表示一下:
var h = g(2 );
這裡的h表示函數h(y)=2y。這樣就有h(5)=10,h(13)=26等。
h(5);
h(13) ;
這個技巧是把需要多個參數的函數形式轉變為接受單一參數的函數鏈,它通常叫做Curring,這是為了紀念Haskell Curry而起的名字,但他並不是第一個提出的1。但是很遺憾的是javascript並不支援這樣的特性。所以要實現這樣的特性需要做一些工作,這些工作並不複雜。主要是把參數儲存起來,等待呼叫函數鏈上的下一個函數時拿出前邊參數繼續傳遞給鏈上的下一個函數,直到最後得到回傳值。先看一下下面的程式碼:
function atarr(a,function atarr(a, index){
var index=index||0,args = new Array(a.length - index);
for(var i in a){
if(i>=index) args[i -index]=a[i];
}
return args;
}
function m(scope,fn){
if(arguments.lengthvar p = atarr(arguments,2);
return function(){
var args = atarr(arguments);
return fn.apply(scope,p.concat(args ));
}
}
測試代碼:
var plus = function(a,b){
return a b;
};
var plus2 = m(null,plus,2);
console.log(plus2(10));
console.log(plus2(0));
//結果
12
2
這樣我們的目標已經實現啦。在上面的atarr函數是將arguments物件中指定位置開始的參數取出並且儲存到一個陣列中。 m函數就是主角,它完成了前面定義的任務,實現了保存函數鏈上的參數並且返接受餘下參數的函數。測試程式碼中的plus函數原先接受a,b兩個參數並回傳a與b之和,即int -> int -> int,而plus2則變成了接受一個參數b與2相加,並回傳2與b之和,即int -> int。
透過上面的一些工作,我們實作了javascript中的Partial Application,在dojo框架中hitch2實作了域綁定和partial。有興趣可以讀它的源碼,也是非常簡單明了的。