Curry 是個很有趣的東西 詳細的介紹可以上維基查看一下: en.wikipedia.org/wiki/Currying

大致的概念是這樣的:

$$ f(x, y) = x + y $$ $$ let \space g(y) = f(2, y) $$ $$ then, g(y) = 2 + y $$ $$ For\space example, g(100) = 102 $$

簡單地說, 就是將函數的某一個參數抽換之後, 可以變成另外一個函數 更極端一點, 甚至可以認為 $$ f(x,y) = f_1(x, f_2(y)) $$

在 Haskell 語言中, 函數原生就是這樣

1
2
3
4
5
6
7
8
9
sumThree x y z = x + y + z
sumTwoPlusTen = sumThree 10

sumTwoPlusTen 1 2
--> gets 13

plusNine = sumTwoPlusTen (-1)
plusNine 100
--> gets 109

老實說, 多數人寫起 Haskell 應該不會感覺很直觀 但這是一個十分有趣的玩法



切入正題, 要如何在JavaScript中使用?

首先我定義一個求兩數字和的函式來做示範

1
2
3
4
5
var sum = function(x, y) {
  return x + y;
};

sum(1, 2); //=> 3

那如果我們要利用上面的函數, 寫另一個函數, (10 + 參數) 呢?

1
var sumPlusTen = sum(10);

當然, 像這樣直接比照 Haskell 的寫法是沒用的, 你只會得到 NaN, 而不是一個 Function

會寫 JavaScript 的人應該都想得到一個簡單的解法:

1
2
3
var plusTen = function(x) {
  return sum(10, x);
}



當然, 我不會特別寫一篇文章來講這麼無聊的事情 讓我們改寫一下 sum, 讓他可以接收不定量參數

1
2
3
4
5
6
7
8
9
10
var sum = function() {
  var i, s = 0;
  for(i = 0; i < arguments.length; i+=1) {
    s += arguments[i];
  }
  return s;
};

sum(1, 2); //=> 3
sum(1, 2, 3); //=> 6

這樣剛剛的寫法就行不通了

不過懂得如何用 apply 的人應該也可以想出來:

1
2
3
var sumPlusTen = function() {
  return sum.apply(null, [10].concat(arguments));
}

把陣列 [10] 接上 arguments, 再利用 apply 傳入 sum !

當我正為自已的聰明才智沾沾自喜的時候, 直譯器打了我一巴掌:

1
2
sumPlusTen(1,2,3);
//=> '10[object Arguments]'

WTF!!!! 原來 arguments 不是一個真的陣列, 所以用 concat 是接不起來的 (我真不知道當初 JavaScript 在設計的時候是怎麼想的, 搞出這種鬼)

解法:

1
2
3
4
5
6
var sumPlusTen = function() {
  var args = Array.prototype.slice.apply(arguments);
  return sum.apply(null, [10].concat(args));
};

sumPlusTen(1,2,3); //=> 16

這是一個神奇的魔術, 把 arguments 轉換成陣列, 成功!

當然身為欲求不滿的程式設計師, 我們不能就此滿足 我們不能忍受每次要用這玩意都要做這麼麻煩的手續

希望可以有這樣一個方法:

1
2
3
4
5
var sumPlusTen = curry(sum, 10);
sumPlusTen(1,2,3); //=> 16

var sumPlus4Plsu5 = curry(sum, 4, 5);
sumPlus4Plsu5(1,2,3); //=> 15



那就來做吧!!

1
2
3
4
5
6
7
8
9
var curry = function(fun) {
  var outArgs = Array.prototype.slice.apply(arguments);
  outArgs = outArgs.slice(1); // old outArgs[0] is 'fun'

  return function(){
    var args = Array.prototype.slice.apply(arguments);
    return fun.apply(null, outArgs.concat(args));
  };
};

執行結果:

1
2
3
4
5
var sumPlusTen = curry(sum, 10);
sumPlusTen(1,2,3); //=> 16

var sumPlus4Plsu5 = curry(sum, 4, 5);
sumPlus4Plsu5(1,2,3); //=> 15

成功!!

稍微說明一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var curry = function(fun) {
  // 將參數列轉換為陣列
  var outArgs = Array.prototype.slice.apply(arguments);

  // 因為第一個參數是要被 curry 的 function, 所以把它丟掉
  outArgs = outArgs.slice(1); // old outArgs[0] is 'fun'

  return function(){
    // 這裡的 arguments 是 curry 後被呼叫的函式所接收的參數
    var args = Array.prototype.slice.apply(arguments);

    // 將我們要放進去的參數, 以及後來被呼叫時傳入的參數接起來
    return fun.apply(null, outArgs.concat(args));
  };
};

如果你不介意汙染到 Function.prototype 也可以這樣寫:

1
2
3
4
5
6
7
8
9
Function.prototype.curry = function() {
  var outArgs = Array.prototype.slice.apply(arguments),
      originFun = this;

  return function() {
    var args = Array.prototype.slice.apply(arguments);
    return originFun.apply(null, outArgs.concat(args));
  };
};

可以得到:

1
2
var sumPlusTen = sum.curry(10);
sumPlusTen(1,2,3); //=> 16

用 CoffeeScript 可以更為簡短

1
2
3
4
5
Function.prototype.curry = ->
  outArgs = Array.prototype.slice.apply arguments
  =>
    args = Array.prototype.slice.apply arguments
    @apply null, outArgs.concat(args)

創用 CC 授權條款
本著作由TeenSuu Lin製作,以創用CC 姓名標示-相同方式分享 3.0 Unported 授權條款釋出。

見人言某語言優雅有感

有些語言可能可以說是好用.靈巧.在programmer能力值高的情況下能產出好的程式但卻很難被歸類為 “優雅”##比如說這麼一個語言混淆應屬於物件的 method 或獨立的 function甚至狂熱試圖把一切都當成物件然後鼓勵一堆人寫出 `3.times do` 這種念起來很順 …… Continue reading

[Note]Setup SSH on Fedora in VirtualBox

Published on January 30, 2015