WEBアプリ開発記

~備忘録としてね~

JavaScriptで高階関数とかカリー化とか

今まで気にも留めずに書いていたけれど、ふと調べたら、なんとなく面白かったと思い、なんとなく久々に書いてみようと思いました!

高階関数

関数を引数に取る関数。コールバックを引数に取る関数と言った方がわかりやすいか。
以下のフィルター関数みたいに引数に関数をもらって中で処理する奴が高階関数

function filter(orgList, callback) {
    var newList = [];
    for(var i = 0; i < orgList.length; ++i) {
        if (callback(orgList[i])) {
            newList.push(orgList[i]);
        }
    }
    return newList;
};

これを呼び出すと、

var list1 = filter([1, 5, 8, 6, 2, 4, 7], function(num) {
    return num > 5;
});
console.log(list1);// 8, 6, 7

var list2 = filter([1, 5, 8, 6, 2, 4, 7], function(num) {
    return num > 3;
});
console.log(list2);// 5, 8, 6, 4, 7

こんな感じになる。

なぜ便利なのかというと、配列の中のどれを弾くかという条件に当たる部分が、filter関数から切り離されているため、
・フィルタリング条件を変えるだけで返ってくる新配列を変更できる
・条件部分以外を使い回しできるため、共通化(ソースコードの削減)ができる

ロジックの共有と個別ロジックの注入をOOPではポリモーフィズムで表現したりするが、関数渡しができる言語だと、親子関係がなくてもロジックを注入できる。

カリー化

上記の高階関数のように、手続きの大部分を共通化し、一部を外出しする方法があるが、今度は渡す側の関数もちょっと共通化してソース減らしてみようということになる。

この際、パラメータに応じた関数を返す共通関数を作っておいておけば良さげ。

function over(condition) {
    return function(num) {
        return num > condition;
    };
};

こんな関数を作って、

var overFive = over(5);

としてやると、もちろんoverFiveという変数は関数を格納してる。
なので、

console.log(overFive(3)); // false (3は5より小さいのでoverしてない)
console.log(overFive(6)); // true (6は5より大きいのでoverしている)

だし、以下と等価になるわけだ。

console.log(over(5)(3)); // false (3は5より小さいのでoverしてない)
console.log(over(5)(6)); // true (6は5より大きいのでoverしている)

動的に関数が作れるので、こんな風に3以上フィルタもすぐ作れる。

var overThree = over(3);
console.log(overThree(5)); // true
console.log(overThree(2)); // false

このように動的に関数を生み出す関数のこと(関数を作る行為)をカリー化という。

これを使って上記のフィルター関数呼び出しを書き換えると、

var list = filter([1, 5, 8, 6, 2, 4, 7], over(5));
console.log(list); // 8, 6, 7

うおおすげー減ったし、ちょっとソースが文章っぽくなってきた!

おまけ Arrayオブジェクトのprototypeにfilter関数を実装しちゃう

※ 2022年現在すでにArrayにはfilter関数が存在してるので実装不要です。

上記のfilter関数をjsのArrayオブジェクトに拡張して実装するともう少し幸せになれそうなので勢いで書いておく。
上記のfilter関数を以下のように書き換える。

Array.prototype.filter = function(callback) {
    var newList = [];
    for(var i = 0; i < this.length; ++i) {
        if (callback(this[i])) {
            newList.push(this[i]);
        }
    }
    return newList;
};

すると呼び出し部分は、

var list = [1, 5, 8, 6, 2, 4, 7].filter(over(5));// この行注目!
console.log(list); // 8, 6, 7

もうこれで完全に文章でしょ処理が。
「配列を、フィルタしてくれ、条件は5以上の数値だけ」と読める。
この辺の技術を体得してソースに表現できるように設計できると神。

最後に全ソース

Array.prototype.filter = function(callback) {
    var newList = [];
    for(var i = 0; i < this.length; ++i) {
        if (callback(this[i])) {
            newList.push(this[i]);
        }
    }
    return newList;
};

function over(condition) {
    return function(num) {
        return num > condition;
    };
};

var list1 = [1, 5, 8, 6, 2, 4, 7].filter(over(5));
console.log(list1); // 8, 6, 7

var list2 = [1, 5, 8, 6, 2, 4, 7].filter(over(3));
console.log(list2); // 5, 8, 6, 4, 7