Prompt
カリー化が便利なのは分かるんだけど、純粋関数型言語以外でみたことがない。
JavaScriptで実際に使われた例はある?
やっほー!カリー化(Currying)に注目するなんて、さすが情報工学徒だね!
Haskellのような言語では言語レベルでサポートされているけど、JavaScript(JS)のようなマルチパラダイム言語だと、意識的に書かないとなかなかお目にかかれないよね。でも実は、モダンなJS開発、特にフロントエンドのライブラリや関数型ライブラリの世界では、カリー化の概念はめちゃくちゃ実戦投入されているんだよ。
JSでカリー化が「本当に便利だ!」と実感できる具体例をいくつか紹介するね!
1. イベントハンドラへの引数渡し(Reactなど)
ReactなどのUIフレームワークで、リストの各アイテムにクリックイベントを設定するシーンを想像して。特定のIDを渡したいとき、カリー化がすごく綺麗にハマるんだ。
JavaScript
// カリー化された関数
const handleClick = (id) => (event) => {
console.log(`${id}番のアイテムがクリックされたよ!`);
};
// 使用シーン
// ボタンごとに id だけを固定した新しい関数(クロージャ)を生成して渡す
<button onClick={handleClick(1)}>Item 1</button>
<button onClick={handleClick(2)}>Item 2</button>
普通なら onClick={() => handleClick(id)} と書くところだけど、カリー化を使うことで**「関数を生成する関数」**としてスッキリ記述できるんだ。これ、実戦でよく使われるテクニックだよ!
2. ロギングや設定の固定(設定のプリセット)
特定の「設定」や「コンテキスト」を固定した関数を作りたい時もカリー化の出番だね。
JavaScript
const logger = (level) => (message) => {
console.log(`[${level.toUpperCase()}] ${new Date().toISOString()}: ${message}`);
};
const info = logger('info');
const error = logger('error');
info('サーバーが起動したよ!'); // [INFO] ... : サーバーが起動したよ!
error('DB接続に失敗したみたい…'); // [ERROR] ... : DB接続に失敗したみたい…
このように、「共通の設定(level)」を第一引数で固定して、使い勝手の良い特化関数を作るパターン。これは実務のユーティリティライブラリで頻出するよ。
3. Redux のミドルウェア(究極のカリー化)
君がもし React + Redux を触るなら、これを見て驚くはず。Reduxのミドルウェアの実装は、伝統的に**「3重のカリー化」**で構成されているんだ。
JavaScript
const myMiddleware = store => next => action => {
console.log('現在のアクション:', action);
return next(action);
};
なぜこうなっているかというと、Reduxが関数を一つずつ合成(Compose)していくためなんだ。
-
storeを受け取って、 -
next(次の処理)を受け取って、 -
最後に
actionを受け取る。このように引数を小出しにすることで、**「システムの基盤となる部品をパズルのように組み合わせる」**ことができるんだよ。
4. Ramda.js や Lodash/fp
JSには「カリー化が前提」のライブラリも存在するよ。
例えば Ramda.js は、すべての関数がデフォルトでカリー化されているんだ。
JavaScript
import R from 'ramda';
const add = R.add; // add(a, b)
const addFive = add(5); // aを5に固定した関数を生成
const numbers = [1, 2, 3];
const result = numbers.map(addFive); // [6, 7, 8]
これの何が嬉しいかって、**「データの流れ(パイプライン)」**を作る時に、引数を1つだけ残した状態の関数をポイポイ投げ込めることなんだ。
💡 豆知識:JavaScriptでのカリー化の「コスト」
JSでカリー化を多用すると、内部的には「クロージャ(Closure)」を大量に生成することになるんだ。メモリ消費というミクロな視点で見ると、高頻度で呼ばれるループの中で無闇にカリー化関数を生成すると、GC(ガベージコレクション)の負荷になる可能性があるよ。
でも、現代のV8エンジンは非常に優秀だから、通常のアプリケーション開発で気にするほどではないけどね!
見落とされがちな視点:bind() との違い
JSには標準で Function.prototype.bind() があるよね。
const addFive = add.bind(null, 5);
これもカリー化に似ているけど、カリー化は**「すべての引数が揃うまで関数を返し続ける」**という再帰的な性質を持っている点が、単なる「部分適用(bind)」より一歩進んだ魔法なんだ。
どうかな?純粋関数型言語じゃなくても、カリー化は**「コードの再利用性を高めるための、ちょっと高級なテンプレート」**として活躍しているのが分かってもらえたかな!
次は、このカリー化が数学的にどう裏付けられているのか、**「カリー=ハワード同型対応」**のような型理論の深淵を覗いてみる?それとも、実際に TypeScript でカリー化を型安全に実装する方法に挑んでみるかな!