カリー化は、複数の引数を取る関数を、引数を1つずつ受け取る関数の連鎖に分解する考え方です。
JavaScriptでは矢印関数と相性が良く、イベント処理や配列操作、API設定の共通化など実用的な場面で役立ちます。
本記事では基礎から実例まで、初心者でも段階的に理解できるように解説します。
カリー化とは?JavaScriptの基本
カリー化の定義
カリー化(currying)とは、複数引数の関数を、引数を1つずつ受け取る関数の列に変換することです。
たとえば2引数の加算関数 add(a, b) を、add(a)(b) のように段階的に呼べる形にします。
これは「一度に全部の引数を渡す」代わりに、「少しずつ引数を渡して関数を準備する」イメージです。
準備した関数は、再利用しやすくなります。
例: 通常の関数とカリー化の関数
// 通常の関数
function add(a, b) {
return a + b;
}
add(2, 3); // 5
// カリー化した関数
const addC = a => b => a + b;
addC(2)(3); // 5
通常の関数との違い
通常の関数は「必要な引数を一度に受け取る」のに対して、カリー化は「引数を1つずつ受け取り、次の関数を返す」点が異なります。
この違いにより、途中まで引数を固定して新しい関数を作れることが大きな利点です。
観点 | 通常の関数 | カリー化された関数 |
---|---|---|
書き方 | function add(a, b) | const add = a => b => … |
呼び方 | add(2, 3) | add(2)(3) |
応用 | 一度に実行 | 一部の引数を固定して再利用 |
カリー化の使い方
カリー化は「先に決められる情報を固定しておき、あとから変化する部分だけを渡す」ときに便利です。
先に固定した引数が、後で何度も使える道具になります。
例えば、10を足す関数を一度作っておけば、何度でも使い回せます。
const add = a => b => a + b;
const add10 = add(10); // ここで「10を足す」という設定を固定
add10(3); // 13
add10(25); // 35
カリー化のメリット
再利用性の向上と読みやすさの向上が主なメリットです。
設定や前提条件を先に固定し、あとからデータを渡すだけで済むため、コードが「設定を作る部分」と「実行する部分」に分かれて理解しやすくなります。
イベントハンドラや配列処理、API共通設定などで特に効果を発揮します。
カリー化と部分適用の違い
カリー化は「関数の形」の話、部分適用は「使い方」の話です。
カリー化されていなくても、部分適用は作れます。
逆に、カリー化された関数は自然に部分適用がしやすいです。
項目 | カリー化 | 部分適用 |
---|---|---|
何か | 関数定義を1引数ずつ受ける形にすること | 一部引数を先に渡して新しい関数を作ること |
例 | const add = a => b => a + b | const add10 = add.bind(null, 10) や a => b => a + b の a を固定 |
関係 | 形を作る前提 | その形を使ったテクニック |
// 部分適用の例(カリー化なしでも可)
function multiply(a, b) { return a * b; }
const double = multiply.bind(null, 2); // a=2 を固定
double(9); // 18
混同しがちですが、カリー化は「定義の仕方」、部分適用は「固定して使う行為」だと考えると整理しやすいです。
JavaScriptでのカリー化の書き方
矢印関数でカリー化
矢印関数は入れ子の書き方が簡潔なので、カリー化と相性が良いです。
引数ごとに矢印を重ねるだけで、段階的に受け取れる関数が作れます。
// 2引数
const add = a => b => a + b;
// 3引数
const calcPrice = tax => shipping => price =>
Math.round(price * (1 + tax)) + shipping;
const with10pctAnd500 = calcPrice(0.1)(500);
with10pctAnd500(2000); // 2700
関数宣言でカリー化
関数宣言で書くと、途中の関数に名前を付けてデバッグしやすくできます。
初心者のうちは関数に名前を付け、処理の流れを言葉にすると理解が進みます。
function add(a) {
function addToA(b) {
return a + b;
}
return addToA;
}
const add5 = add(5); // addToA が返る
add5(7); // 12
引数の順番を設計するコツ
「先に決めたいものを先の引数、最後に変わるデータ」を後ろの引数に置くと使いやすくなります。
設定を先に固定し、データを最後に渡すと、読み手の頭の中でも順序が素直に並びます。
// 良い例: 設定 -> データ
const inRange = min => max => n => n >= min && n <= max;
const teens = [10, 11, 12, 13, 19, 22].filter(inRange(10)(19));
// 別の良い例: 検査条件 -> データ
const hasProp = key => obj => key in obj;
[{a:1}, {b:2}].filter(hasProp('a')); // [{a:1}]
データを先に、設定を後ろに置くと固定しづらくなりがちです。
最後の引数は「一番よく変わるもの」と覚えると自然に設計できます。
シンプルなカリー化ヘルパー関数の作り方
毎回 a => b => … と書くのが面倒なときは、関数を自動でカリー化する小さなヘルパーが役立ちます。
まずは基本版(任意の個数に対応)
const curry = fn => {
return function curried(...args) {
if (args.length >= fn.length) {
return fn(...args);
}
return (...rest) => curried(...args, ...rest);
};
};
// 使い方
const add3 = (a, b, c) => a + b + c;
const add3C = curry(add3);
add3C(1)(2)(3); // 6
add3C(1, 2)(3); // 6
add3C(1)(2, 3); // 6
注意点
fn.length は「定義された引数の数」を使います。
デフォルト引数や残余引数(…args)を多用すると期待と異なる場合があるため、最初はシンプルな関数からカリー化すると安心です。
カリー化の実用例
イベントハンドラの設定を簡単に
イベントハンドラでは「誰のイベントか」という文脈を固定しておくと便利です。
ユーザーIDや要素の種類などを先に束ね、イベント発生時に使う形にできます。
const handleClick = userId => event => {
console.log(`[click] user=${userId}`, event.currentTarget);
};
// userId を固定してからイベントを登録
const button = document.querySelector('#save');
button.addEventListener('click', handleClick('u-123'));
このようにしておくと、別のボタンにも同じパターンで簡単に使い回せます。
異なるIDで同じ処理を量産しやすいのが利点です。
配列処理(map/filter)で便利に
配列処理は、条件や変換ロジックを先に固定しておくと読みやすくなります。
filter は条件を、map は変換規則を先に準備するイメージです。
// filter の例: 最小値を固定してから判定
const atLeast = min => n => n >= min;
[1, 5, 10, 20].filter(atLeast(10)); // [10, 20]
// map の例: 税率を固定して価格を変換
const withTax = tax => price => Math.round(price * (1 + tax));
[100, 250].map(withTax(0.1)); // [110, 275]
// map の例: プロパティ取り出し器(pluck)
const pluck = key => obj => obj[key];
const users = [{name: 'A'}, {name: 'B'}];
users.map(pluck('name')); // ['A', 'B']
同じ条件を別の配列でも再利用でき、誤記も減らせます。
フォーム入力のバリデーション
バリデーション規則を先に固定し、値だけをあとで渡すと、入力欄ごとに明確なルールを用意できます。
const required = value => value.trim().length > 0;
const minLen = len => value => value.length >= len;
const matchRe = re => value => re.test(value);
const validateUsername = value =>
required(value) && minLen(3)(value);
const validateEmail = matchRe(/^[^@]+@[^@]+\.[^@]+$/);
validateUsername('hi'); // false
validateUsername('hiro'); // true
validateEmail('a@b.com'); // true
関数を組み合わせて新しいルールを作る際も、1つずつの動作が分かれているためテストしやすくなります。
APIリクエストの共通設定を固定
API呼び出しでは、ベースURLや共通ヘッダーを先に固定すると、各エンドポイントの呼び出しが短くなります。
const makeRequest = baseUrl => defaultOptions => path => options =>
fetch(baseUrl + path, { ...defaultOptions, ...options });
const api = makeRequest('https://api.example.com')({
headers: { 'Content-Type': 'application/json' }
});
// 以降は path と個別オプションだけで済む
api('/posts')({ method: 'GET' });
api('/posts')({
method: 'POST',
body: JSON.stringify({ title: 'Hello' })
});
共通部分の記述が1か所に集まるため、変更や確認が簡単になります。
ログ出力のプレフィックスを固定
環境や機能名をログの前に付けると、調査が楽になります。
プレフィックスを固定したロガーを用意しましょう。
const makeLogger = prefix => (...args) =>
console.log(`[${prefix}]`, ...args);
const logApp = makeLogger('APP');
const logAuth = makeLogger('AUTH');
logApp('started'); // [APP] started
logAuth('login ok'); // [AUTH] login ok
どの機能からのログかが一目で分かるようになり、開発中のトラブルシュートがはかどります。
JavaScript初心者のつまずきと対策
関数が関数を返す流れを理解する
「関数を呼ぶと次の関数が返る」という流れが最初の壁です。
add(2) の結果は「b を受け取る関数」で、add(2)(3) で初めて計算が完了すると捉えましょう。
const add = a => b => a + b;
const add2 = add(2); // ここではまだ「関数」
typeof add2; // 'function'
const result = add2(3); // 5
一段ずつ実行して確認すると、返ってくるものが何かを体で理解できます。
途中結果を変数に入れて観察するのがコツです。
括弧の位置と呼び出し順に注意
カリー化関数は f(a)(b)(c) のように、引数ごとに括弧を重ねます。
f(a, b) と書くと別物なので注意が必要です。
const add = a => b => a + b;
// 正しい
add(1)(2); // 3
// 間違い(カリー化関数に2引数同時は不可)
add(1, 2); // 期待通りにならない
イベントハンドラでも、addEventListener には「関数そのもの」を渡す点に注意します。
呼び出し結果を渡してはいけません。
// 良い例
button.addEventListener('click', handleClick('u-1'));
// 悪い例(すぐ実行されてしまう)
button.addEventListener('click', handleClick('u-1')());
過剰なカリー化を避ける判断
カリー化は便利ですが、深すぎる入れ子や一度きりの処理までカリー化すると、かえって読みにくくなります。
2〜3段程度で十分な場面が多いです。
「設定を固定して何度も使うか」を基準にしましょう。
判断の目安 | カリー化が有効 | カリー化しない方が読みやすい |
---|---|---|
再利用 | 同じ設定を何度も使う | 一度しか使わない |
引数の安定度 | 固定しやすい順序がある | 毎回すべて変わる |
段数 | 2〜3段 | 4段以上になる |
目的 | イベントや配列処理、設定の固定 | 単発の単純計算 |
デバッグしやすい書き方
途中結果を変数に置き、関数に名前を付けるだけで理解が一気に楽になります。
1行に詰め込まず、段階を言葉にします。
// 詰め込みすぎ
const price = qty => tax => unit => Math.round(qty * unit * (1 + tax));
// 段階を分ける
function price(qty) {
function withTax(tax) {
function withUnit(unit) {
const raw = qty * unit;
const taxed = Math.round(raw * (1 + tax));
return taxed;
}
return withUnit;
}
return withTax;
}
// 途中でログを見る
const for3Items = price(3);
console.log(for3Items.name); // withTax
小さな名前付き関数に分けると、スタックトレースやログが読みやすくなります。
まとめ
カリー化は、引数を段階的に受け取ることで「設定の固定」と「再利用のしやすさ」を実現する技法です。
JavaScriptでは矢印関数で簡潔に書け、イベントハンドラ、配列処理、フォーム検証、API共通設定、ログ整形など多くの実用場面で役立ちます。
引数の順番は「先に決めたいものから、最後はよく変わるデータ」という考え方で設計すると使いやすくなります。
深追いしすぎず、まずは2〜3段のカリー化から活用して、コードの見通しと再利用性を高めていきましょう。