プログラミングの「カリー化」ってなに?実例を用いて詳しく紹介

今回は、

  • カリー化ってなに?
  • どういうふうに使うの?
  • どんなときに使うの?

こんな悩みを解決するために

カリー化とはなんぞやを紹介していきます。

僕も初めて聞いた時は

まさ

カレー化?なんだか美味しそう。
言葉から全くイメージがわかない

と思ってました。

実際に調べてみると、かなり使えそうだったので、

学習した内容をしっかりまとめて紹介していきます。

目次

カリー化とは?

カリー化 (currying, カリー化された=curried) とは、複数の引数をとる関数を、引数が「もとの関数の最初の引数」で戻り値が「もとの関数の残りの引数を取り結果を返す関数」であるような関数にすること(あるいはその関数のこと)である。

https://ja.wikipedia.org/wiki/%E3%82%AB%E3%83%AA%E3%83%BC%E5%8C%96
まさ

少し分かりづらいですが大丈夫です。
例を見るとすんなり理解できます。

複数の引数を取る関数を以下とした場合

function add(a, b) {
    return a + b
}

add(1, 2); 

カリー化すると

function curriedAdd(a){
    return function(b){
        return a + b
    }
}

curriedAdd(1)(2);  // 3

こんな感じになります。

コードの記述の違いをみて理解できたのではないでしょうか。

要するに複数引数を受け付ける関数を単一の引数のみ受け付ける形で書き直すってことです。

では続いて処理の流れを見てみましょう。

add関数の場合は

  1. aに1をbに2を受け取る
  2. a + b(1 + 2)が行われ計算結果の3が返される

curriedAddの場合は

  1. curriedAdd(a)が最初の引数1を受け取る。a=1
  2. function(b)が返される。
    curriedAdd(1)の実行結果としてa=1のプロパティを持ったfunction(b)が返される
  3. function(b)が次の引数2を受け取る。b=2
  4. a+b (1+2)が行われ計算結果の3が返される

実際に動きを見ながら確認したい場合は、上記コードをコピペしてブラウザ経由でステップ実行してみてください。

カリー化の実用例

ここまでカリー化を紹介しましたが、まだぼんやり理解しただけで、

そのメリットが見えてないかと思います。

そこで、いくつか実用例を紹介しながらメリットを上げてみます。

URLを生成に3つの引数を取る関数のカリー化

function getUrl(protocol, hostname, pathname) {
    return `${protocol}${hostname}${pathname}`;
}

const url1 = getUrl('https://', 'www.yahoo.co.jp', '/image');
const url2 = getUrl('https://', 'www.google.com', '/imghp?hl=ja&ogbl');
const url3 = getUrl('https://', 'www.bing.com', '/images/feed');

例えばこの例でいうと、プロトコルってどれも一緒ですよね。

カリー化して共通部分をまとめてあげると、

function curry(protocol) {
    return function (hostname, pathname) {
        return `${protocol}${hostname}${pathname}`;
    }
}

const url_curry = curry('https://');

const url1 = url_curry('www.yahoo.co.jp', '/image');
const url2 = url_curry('www.google.com', '/imghp?hl=ja&ogbl');
const url3 = url_curry('www.bing.com', '/images/feed');

こんな感じで書き換えることができます。

カリー化をしてあげることでコードの可読性UPやコードの重複削減が見込めます。

合計重量を計算する関数のカリー化

let totalWeight = 0;
const addWeight = function (weight) {
    totalWeight += weight;
};

addWeight(1.1);
addWeight(2.2);
addWeight(3.3);
addWeight(4.4);

console.log(totalWeight);  

こんなプログラムがあるとします。

まさ

プログラムとしては問題ないけど、
addWeightを呼び出すたびに、totalWeight+=weightが実行されてるね。
最後にtotalWeightを呼び出す時だけに計算してほしいな。

上記例では関数内の処理が軽いので体感しづらいですが、

関数内に重い処理があった場合、addWightを呼び出すたびに実行されてしまいます。

ではカリー化した状態を見てみましょう。

function curryWeight(fn) {
    let weightQueue = [];
    return function () {
        if (arguments.length === 0) {
            return fn.apply(null, weightQueue);
        } else {
            weightQueue = weightQueue.concat([...arguments]);
        }
    }
}

function addWeight() {
    let totalWeight = 0;
    for (let i = 0, len = arguments.length; i < len; i++) {
        totalWeight += arguments[i];
    }
    return totalWeight;
}

const _addWeight = curryWeight(addWeight);
_addWeight(1.1);
_addWeight(2.2);
_addWeight(3.3);
_addWeight(4.4);
console.log(_addWeight())

簡単にコードの内容を紹介します。

curryWeightは引数の「あり」・「なし」を判定し、
引数が「ある」場合はキュー(weightQueue)に追加していき、
引数が「ない」場合はfnにweightQueueを渡して実行します。

addWeightは、引数をループ処理で加算し、最終結果を返します。

上記を理解した上で「_addWeight」を見てください。

_addWeightに引数がある場合は、weightQueueに値が貯められていき、

_addWeightを引数無しで実行した場合には、addWeight(null,args=weightQueue)が実行され、
合計重量が返されます。

このように必要なときにだけ計算をさせるテクニックを遅延評価といいます。

遅延評価を使用することで、プログラムの無駄な処理を省くことができるので、

よりパフォーマンスが出せるプログラムを書くことができるようになります

さいごに

エンジニアとして成長していく上で大事な、

  • 冗長なプログラムを書かない
  • プログラムの可読性を上げる
  • プログラムの最適化を行う

に貢献してくれるカリー化を紹介してみました。

ぜひ、普段のコーディング時に取り入れてみてください。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次