🍛美味しそうな技...?
そうです。カリー化について説明します。カリー?なにそれおいしいの?と思いましたね。カリーは確かにおいしいです。
🍛どんな記述?
const curry = a => b => a + b;
curry('カレー')('ライス');
// --> カレーライス
例えばこういった記述です。
初見ではナニコレ!?と驚きますね。
この記述は一部省略してあります。省略せずに書くと、
const curry = (a) => {
return (b) => {
return a + b;
}
}
こうなります。アロー関数の省略記法だと分かりますね。
簡単に説明すると、
引数がひとつのときそれを囲む
()
が省略でき、関数内の記述が一行のとき{return ...}
の{}
とreturn
が省略できる
というものです。
結果すべての()
, {}
, return
が省略でき、上記の関数を省略記法を用いて記述すると、下のようになります。
const curry = a =>
b =>
a + b ;
これで、この形が出来上がりました。
const curry = a => b => a + b;
🍛どう便利ナン?(カレーだけに
簡潔に言うと、カリー化は、部分適用を簡単に記述できます。
🍛部分適用って何?
部分適用とは、一言でいうと
既存の関数の引数の一部を固定し、新たな関数を作ること
です。
数学が好きな方は予選決勝法や一文字固定法などをイメージしてくださると伝わりやすいかもしれません(言いたかっただけですが、感覚が酷似しています)。
例を見ていただいた方が早いと思うので、こちらをご覧ください。
const ryori = function(a, b){
// 2つの具材を合わせる関数
return a + b;
}
const cheeseRyori = ryori.bind(null, 'チーズ');
// 2つの具材のうち1つをチーズに固定する、cheese料理関数
cheeseRyori('カレー'); // チーズカレー
// cheese料理関数と引数のカレーを合わせて、チーズカレーを作る
cheeseRyori('納豆'); // チーズ納豆
// cheese料理関数と引数の納豆を合わせて、チーズ納豆を作る
こんな感じで、ryori関数
の引数を固定し、チーズ料理だけを作る関数
を作ることができます。
同様に、
const spaghettiRyori = ryori.bind(null, 'スパゲッティ');
とすればスパゲッティ料理だけを作る関数
を作ることができます。
そして、これにカリー化を組み合わせると、
const ryori = a => b => a + b;
const cheeseRyori = ryori('チーズ');
cheeseRyori('カレー');
cheeseRyori('納豆');
このようになり、かなり可読性が増したと思います。
これがカリー化のメリットですね。
🧀注意
(2022/11/11追記)間違えた説明をしていました。訂正を加えて元の文章は折り畳みにしておきます。
間違えていた内容です。
ひとつ注意が必要なのですが、ここでアロー関数を使わずに`function`を使ったのは理由があります。理由がわかる人は飛ばしてください。(もしくは教えてください。)通常の関数とアロー関数の大きな違いのひとつとして、thisの指すものの違い
があります。
https://qiita.com/suin/items/a44825d253d023e31e4d
この記述ではbind()
メソッドを用いています。
bind()
メソッドは、呼び出された際に this キーワードに指定された値が設定される新しい関数を生成します。
おそらくこのthisの参照先の違いが原因で、アロー関数では思ったように動きませんでした。
アロー関数を用いた場合でも問題なく動きました。
既に記事をご覧になった方、申し訳ございません。
🍛実際いつ使うん?
ここまでの説明ではカリー化、というより部分適用の魅力が十分に伝わっていないかもしれません。
先ほど部分適用とは
既存の関数の引数の一部を固定し、新たな関数を作ること
と言いましたが、これが同時にメリットにも繋がります。
もうひとつ例を挙げます
日付、料理名、美味しいか否か(真偽)を受け取り、文字列を生成する関数です。
この文字列を用いて料理日記を付けることを想定してください。
まずはカリー化も部分適用も用いない場合の記述です。
const cookMemo = ( date, dish, isDelicious) =>
`${date}に${dish}を作りました。美味し${isDelicious?'':'くな'}かったです。`;
let now = new Date();
let today = `${now.getMonth()+1}月${now.getDate()}日`;
cookMemo(today, 'カレー', false);
// --> 11月8日にカレーを作りました。美味しくなかったです。
cookMemo(today, '納豆', true);
// --> 11月8日に納豆を作りました。美味しかったです。
この記述だと、最後の文字列を生成する段階で、引数まで見ないと今日の日付で文字列を生成することが分かりません。
しかし、これをカリー化と部分適用を用いて記述してみます。
const cookMemo = date => dish => isDelicious =>
`${date}に${dish}を作りました。美味し${isDelicious?'':'くな'}かったです。`;
let now = new Date();
let today = `${now.getMonth()+1}月${now.getDate()}日`;
const todayCookMemo = cookMemo(today); // 部分適用!
todayCookMemo('カレー')(false);
// --> 11月8日にカレーを作りました。美味しくなかったです。
todayCookMemo('納豆')(true);
// --> 11月8日に納豆を作りました。美味しかったです。
どうでしょう。便利そうに感じませんか?
cookMemo
関数のから日付を固定し、新しく今日のメモを作るtodayCookMemo
関数を作りました。これが部分適用です。
また関係ないですが、カリー化に至っては別言語を書いているようで楽しいです。
🍛以上!🍛
おなかがすいたのでおわります。
🍛追記!(2022/11/11)
コメント欄にて、カリー化がうまく働く例を教えていただきました。
const add = n => m => n + m;
[1, 2, 3].map(add(10)); // [ 11, 12, 13 ]
簡潔で見やすいですし、10じゃなくて20足したい!なんて場合はadd()
の引数を変えてあげれば良いと一目瞭然なので、便利そうだと感じました。
ただ、同時にカリー化は必要ないという意見も頂いております。
カリー化を敢えて用いることによるメリットや、カリー化以外の手段との比較など意識して最適解を選べるようにしたいですねー!
🍛参考記事