2017年11月9日木曜日

javascriptで簡単なカリー化関数を作った(ES6版)

最近、scalaからjavascriptに不本意ながら引っ越してきました。初めは嫌々ながらだったんですけど、ES6になってめっちゃ書きやすくなってるんですね。知りませんでした。
そんなわけで今回は、カリー化の仕組みがjavascriptの標準になさそうだったので、簡単にカリー化したいと思って調べてみると。 結構良さそうなのが、ありました。(Qiita: JavaScript でカリー化)
そのまま使っても良かったんですけど、僕はjavascriptでは、できるだけthisを使わないようにしているので、リンクの記事のcontextがいらないのと、その場合の空の括弧()がいらないので、上の記事のコードを参考に改造しました。
あと、他の言語から来ると、関数宣言の括弧の中にない引数をargumentsを使って取るのは気持ち悪いので、ES6の可変長引数を使ってこんな風にしてみました。
Function.prototype.curry = function (...args) {
  let func = this;
  function partial(...args) {
    return (args.length >= func.length)
      ? func.apply(null, args)
      : function(...ret_args) {
          return partial.apply(null, [...args, ...ret_args]);
        }
  }
  return partial.apply(this, args);
};
とりあえず、こんな感じにして動作確認
function sum1(a, b, c) {
  return a + b + c;
}
let sum2 = ((a, b, c) => a + b + c).curry()

console.log(sum1.curry(4, 5, 6))  // <- 6
console.log(sum1.curry(4)(5)(6))  // <- 15
console.log(sum2(10)(11)(12))  // <- 33
これでいいんだけど、あまりビルトインのprototypeを勝手に拡張すんな!としつこくMDN(ココココ)に書いてあるので、僕は潔癖でないのでそういうの嫌なんだけど、まあ後でcurryメソッドとか正式に追加されたらそれはそれで嫌なので、ただの関数版も作ってみた。
scalaのimplicit conversionが懐かしいぜ・・・
function curry(fn) {
  return function partial(...args) {
    return (args.length >= fn.length)
      ? fn.apply(null, args)
      : function(...ret_args) {
          return partial.apply(null, [...args, ...ret_args]);
        }
  }
};
こっちの使い方はこんな感じ
let sum3 = curry((a, b, c) => a + b + c)

// sum1は、上のを使いまわし
console.log(curry(sum1)(4, 5)(6))  // <- 15
console.log(curry(sum1)(1)(2)(3))  // <- 6
console.log(sum3(20)(21)(22))  // <- 63
console.log(sum3(2)(4, 6))  // <- 12
まあ、難しいこと考えなければこれで良いような気がします。
関数版は、憎たらしいthisも使わないですんでるし。
昔、10年くらい前には、
{javascriptって何でもfunctionで出来てて意味わかんねー} 
とかって思ってたんすけど、その基本設計と最近のES5,ES6と続く言語の拡張により俄然、モダンな書き方ができる言語に変化してきてるんですね。