lodash を使った JavaScript でのカリー化と部分適用
関数型プログラミングにはカリー化と呼ばれる手法があります。
これは lodash などを用いた高階関数によるデータ処理を行う際に役に立ちます。また、 lodash ではカリー化を簡単に行える関数も用意されています
今回は JavaScript でのカリー化と lodash を使った実現方法について説明します。
また、カリー化と似た部分適用、 バインディングについても説明しています。
カリー化
カリー化というのは複数の引数をとる関数を、引数が "もとの関数の最初の引数" で戻り値が "もとの関数の残りの引数を取り結果を返す関数" であるような関数にすることことを言います。
これは言い換えると
2 つ以上の引数を持つ関数を 1 つの引数の関数の組み合わせになおすともいえます。
実際に例を見たほうが分かりやすいと思うので、次のような 2 つの引数を取る関数をカリー化してみます。
function plus(a, b) { return a + b; }カリー化は戻り値として無名関数を返すことによって実現します。
function curried_plus(a) { return function(b) { return plus(a,b); }; }戻り値の関数内では関数内でローカルな引数 a を使っていますが、 クロージャーという技術によって、うまいこと残してくれるようになっています。
plus(2, 3)
と書いていたものが curried_plus(2)(3)
と書けるようになりました。これがカリー化です。
plus(2,3); // 5 curried_plus(2)(3); // 5カリー化に似たものに 関数の部分適用 という用語もあります。
こちらは
複数の引数を持つ関数の一部の引数にだけに実引数を適用する操作です。
先ほどの plus の関数の引数の一つを 2 に固定した関数を作ってみます。
function partial_plus(b) { return plus(2, b); } partial_plus(3); // 5この partial_plus という関数を作る操作が部分適用です。
ざっくり書くと次のような感じです。
カリー化 : f(a, b, c) → g(a)(b)(c) 部分適用 : f(値1, 値2, c) → g(c)
カリー化の用途
カリー化、部分適用の説明をしましたが、私は実用主義ですので、実は言葉の定義とかあまり興味ないです。大事なのは「それがなんの役に立つか?」というところでしょう。
カリー化が役に立つ場面は 高階関数を使ったデータ処理 を行う時です。
この処理方法が非常に重要なのですが、そちらについては前回の記事に書いているので、そちらを見て下さい。 高階関数を使ったデータ処理に渡す関数の多くは引数が一つだけです。 それに対して、複数の引数をとる関数を適用しようとすると次のようになります。
_.map([3,2,9,6], function(e) { return plus(2, e); }); // [ 5, 4, 11, 8 ]これはこれで問題は無いのですが、カリー化した関数があると次のように書けます。
_.map([3,2,9,6], curried_plus(2));
このように、要求される関数の形式と使いたい関数の引数の数が違う場合にカリー化は使えます。カリー化した関数を使う方法は見やすく、分かりやすいのではないかと思います。
とはいえ、わざわざ関数を作ったりするのはちょっと面倒です。
関数型言語である Haskell や F#(OCaml) などでは 関数は勝手にカリー化されていて、最後の引数を省略すればいいだけなので、お手軽に使えます。
JavaScript にはそんな機能はないのですが、関数型のデータ処理を進める lodash というライブラリーがあり、一発でカリー化してくれる関数が用意されています。
次章から lodash を利用した方法について解説していきます。 なお、 lodash 自体の説明は前述のリンク先の記事をご覧ください。
lodash の利用
lodash を使って カリー化(curry)、 関数の部分適用(partial)を行う方法について紹介します。また、それらと用途が似ている バインディング(bind) ついても説明しています。
curry : カリー化
lodash の curry 関数でカリー化することができます。先ほどの plus を curry を使ってカリー化してみます。
_.map([3,2,9,6], _.curry(plus)(2)); // [ 5, 4, 11, 8 ]引数が 3 つの場合は次のようになります。
function plus3(a, b, c) { return a+b+c; } var curried_plus3 = _.curry(plus3); var ary = [3,2,9,6]; _.map(ary, curried_plus3("a")("b")); // [ 'ab3', 'ab2', 'ab9', 'ab6' ] _.map(ary, curried_plus3("a", "b")); // [ 'ab3', 'ab2', 'ab9', 'ab6' ]データ処理に使いたい引数の位置が最後ではない場合、 記入子(placeHolder) を使って対象を変えることもできます。
_.map(ary, curried_plus3("a")(_, "b")); // [ 'a3b', 'a2b', 'a9b', 'a6b' ] _.map(ary, curried_plus3(_, "a")(_, "b")); // [ '3ab', '2ab', '9ab', '6ab' ]ただ、 この placeHolder は少し指定の仕方がわかりづらいです。 使いたい引数を指すのではなく、引数の場所を一つ後ろと入れ替えるようになっています。
function plus4(a,b,c,d) { return a+b+c+d; } _.curry(plus4)('a')('b')(_, 'c')('d'); // abdc _.curry(plus4)('a')(_, 'b')('c')('d'); // acbd _.curry(plus4)(_, 'a')('b')('c')('d'); // bacd
partial : 部分適用
lodash では関数の部分適用を行うための partial という関数も用意されています。これは引数を固定した関数を返します。
var partial_plus3 = _.partial(plus3, 'a', 'b'); _.map([3,2,9,6], partial_plus3); // [ 'ab3', 'ab2', 'ab9', 'ab6' ]使い分けとしては、作成した関数に名前をつけて使うのであれば、 カリー化の方が引数を変えられるので、柔軟性が高いです。
ただ、直接使うのであれば、どちらを使ってもあまりかわりません。
_.map([3,2,9,6], _.curry(plus)(2)); // [ 5, 4, 11, 8 ] _.map([3,2,9,6], _.partial(plus, 2)); // [ 5, 4, 11, 8 ]placeHolder を使う場合は partial の方が指定の仕方がわかりやすいです。
_.map([3,2,9,6], _.partial(plus3, 'a', _, 'b')); // [ 'a3b', 'a2b', 'a9b', 'a6b' ] _.map([3,2,9,6], _.partial(plus3, _, 'a', 'b')); // [ '3ab', '2ab', '9ab', '6ab' ]
bind : バインディング
そのままでは使えない関数、メソッドなどをうまく適用させることをバインディングと呼びます。引数が合わない関数を使えるようにするカリー化や部分適用もバインディングの一種なのですが、 lodash でバインディングという場合は オブジェクトのメソッドを適用させることを指すようです。
bind 関数はオブジェクトとメソッドをまとめた関数を作成します。 これを利用するとオブジェクトのメソッドをデータ処理に使えるようになります
function Person(name, age) { this.name = name; this.age = age; this.add_age = function (n) { return this.age + n; }; } var user = new Person('taro', 30); _.map([3, 2, 9, 6], _.bind(user.add_age, user)); // [ 33, 32, 39, 36 ]なお、 バインディングは lodash の Ver. 3 と ver. 4 でやり方が変わりました。 古いバージョンを使っている場合は注意してください。
サンプルコード
説明で使用したサンプルのコードは以下のリンクからダウンロード(リンク先を保存)できます。 適当なディレクトリーを作って、そこにサンプルのファイルをおき、プロジェクトの作成とインストールを行います。$ npm init -y $ npm install lodashサンプルを実行は node コマンドで行います。
$ node lodash_sample.js各コマンドの詳しい説明に関しては、以前の記事を見て下さい。
- 関連記事
Facebook コメント
コメント