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)していくためなんだ。

  1. store を受け取って、

  2. next(次の処理)を受け取って、

  3. 最後に 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 でカリー化を型安全に実装する方法に挑んでみるかな!