前言
Curry
化指的是將接受多個參數的函數轉換成可以依次傳入參數的函式,以下見例子。
1 2 3 4 5
| var add = function(x , y){ return x + y ; } add(1,2) ;
|
Curry 化後
1 2 3 4 5 6
| var add = function(x){ return function(y){ return x + y ; } } add(1)(2);
|
這樣做有什麼好處?好處在於我們利用不同的參數來創造不同的函式。
1 2 3 4
| var addOne = add(1) ; var addTwo = add(2) ; addOne(1) ; addTwo(1) ;
|
許多函式庫有提供Curry
化的功能,像是lodash
等等的函式庫。
1 2 3 4 5 6 7 8
| var add = function(x , y , z){ return x + y + z ; } var curryAdd = _.curry(add) ; curryAdd(1,2,3); curryAdd(1,2)(3); curryAdd(1)(2)(3);
|
接下來透過實作來了解是如何辦到 Curry 功能的。
實作
首先無論如何先回傳一個函式
1 2 3 4 5
| var curry = function(fn){ return function(){ } }
|
再來我們要知道什麼時候是參數已經傳完的情況並且作處理,若參數已經傳完則利用 apply 執行函式。
在這邊先 slice 一份 arguments,而由於 arguments 不是陣列而是物件,因此不可以直接對 arguments 做 slice 。
1 2 3 4 5 6 7 8
| var curry = function(fn){ return function(){ var args = Array.prototype.slice.call(arguments, 0); if ( args.length >= fn.length ) { return fn.apply(null, args); } } }
|
若參數還沒傳完,這時候要回傳個函式讓使用者繼續呼叫
1 2 3 4 5 6 7 8 9 10 11 12
| var curry = function(fn){ return function(){ var args = Array.prototype.slice.call(arguments, 0); if ( args.length >= fn.length ) { return fn.apply(null, args); } else { return function() { } } } }
|
若再次呼叫這個函式,則直接把帶入的參數連接到之前的參數後面,並再次呼叫原先的 curried 函式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| var curry = function(fn){ return function curried(){ var args = Array.prototype.slice.call(arguments, 0); if ( args.length >= fn.length ) { return fn.apply(null, args); } else { return function() { var args2 = Array.prototype.slice.call(arguments, 0); return curried.apply(null, args.concat(args2)); } } } }
|
如此一來就完成了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| var curry = function(fn){ return function curried(){ var args = Array.prototype.slice.call(arguments, 0); if ( args.length >= fn.length ) { return fn.apply(null, args); } else { return function() { var args2 = Array.prototype.slice.call(arguments, 0); return curried.apply(null, args.concat(args2)); } } } } var add = function(x , y ,z){ return x + y + z ; } var add = curry(add); console.log(add(1,2)(3)); console.log(add(1)(2,3)); console.log(add(1)(2)(3)); console.log(add(1,2,3));
|
不限參數
若我們希望這個函式不限定參數數量
1 2 3
| add(1) add(1)(2) add(1)(2)(3)
|
以上的方法是不合理的,因為它不知道現在該繼續回傳函式還是該回傳結果,我們可以提供個函式讓它知道已經執行完要得到結果。
1 2 3
| add(1).value() add(1)(2).value() add(1)(2)(3).value()
|
首先一樣先回傳一個函式,並且讓這個函式回傳自己以便繼續傳入參數,且另外給這個函式一個 value 函式。
1 2 3 4 5 6 7 8 9
| var add = function(){ var curried = function(){ return curried ; } curried.value = function(){ } return curried ; }
|
在每次帶入參數後將值加總,並在 value 函式回傳結果即可。
1 2 3 4 5 6 7 8 9 10
| var add = function(sum){ var curried = function(num){ sum += num ; return curried ; } curried.value = function(){ return sum ; } return curried ; }
|
如此一來即可不斷對 add 帶入參數
1 2 3 4 5 6
| var myAdder = add ; for( var i = 0; i < 10; i ++) { myAdder = myAdder(10); } myAdder(10);
|
利用 arguments 來處理一次帶入多個參數
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| var add = function(){ var sum = 0 ; for ( var i = 0 ; i < arguments.length ; i ++ ){ sum += arguments[i] ; } var curried = function(){ for ( var i = 0 ; i < arguments.length ; i ++ ){ sum += arguments[i] ; } return curried ; } curried.value = function(){ return sum ; } return curried ; } add(1).value() add(1)(2).value() add(1)(2)(3).value() add(1,2,3)(4).value() add(1)(2,3,4).value()
|
參考
Gettin’ Freaky Functional w/Curried JavaScript