🍋

【TypeScript】クロージャ・カリー化・部分適用bindの違いを整理する

に公開

以前「クロージャというよりも bind で束縛した感じの…カリー化に近いのでは?」というコメントをいただきました。
コメントをきっかけに改めてコード例と構造の違いをまとめました。

TL;DR

  • どれも「関数を返す関数」で外側で一部の値を固定する技法
  • 技術的にはすべてクロージャを利用している
  • 設計パターン的には以下のように分類:
    • クロージャ:値や設定をスコープに閉じ込める仕組み
    • カリー化:n引数関数を1引数ずつ返す関数の連鎖に変換
    • 部分適用:多引数関数の一部の引数を先に固定し、新しい関数を返す

背景

以前の記事で、次のようなコードを紹介しました:

function handleError(context: string) {
  return (err: unknown) => {
    console.error(`${context}中にエラーが発生しました:`, err);
  };
}

// 使い方
await clearTask().catch(handleError("タスク削除"));

外側で context を固定し、内側で err を処理しています。
技術的にはクロージャですが、設計パターン的には「部分適用」に近い形です。

クロージャの例

クロージャは外側の変数をスコープに保持し、内側関数で参照可能にする仕組みです。

function makeLogger(prefix: string) {
  return (msg: string) => {
    console.log(`${prefix}: ${msg}`);
  };
}

const infoLogger = makeLogger("INFO");
infoLogger("Hello"); // INFO: Hello

カリー化の例

カリー化は、n引数関数を「1引数ずつ」呼ぶ関数の連鎖に変換する技法です。

const add = (x: number) => (y: number) => x + y;

add(2)(3); // 5

const add2 = add(2);
add2(3); // 5

// こちらの書き方でも同様
function add(a: number): (b: number) => number {
  return (b) => a + b;
}

add(2)(3); // 5

bind を使った部分適用の例

a = 2 を固定した新しい関数を返します。
なお、bind の戻り値の型が any のため型安全性に欠点があります。

function add(a: number, b: number) {
  return a + b;
}

const addTwo = add.bind(null, 2);

addTwo(10); // 12
addTwo("ABC"); // 型チェックがされない

特徴

手法 概要 特徴
クロージャ 外側関数の変数をスコープに閉じ込める 固定する値や数は自由
カリー化 n引数関数を1引数ずつ返す関数に変換 常に1引数ずつ渡す
部分適用 多引数関数の一部を固定し新しい関数を返す 固定する数は任意

使い分けの観点

パターン よく使う場面 メリット 注意点
クロージャ 設定・状態を閉じ込める 柔軟・型安全 濫用でわかりにくくなる
カリー化 汎用的ユーティリティ 柔軟に部分適用 書きすぎると複雑に
部分適用 引数を先に固定したい 明快 bind は型安全性に欠点

まとめ

技術的には全部クロージャですが、設計的には「何を固定・注入するか」「何を隠すか」によって部分適用・カリー化・クロージャを使い分けます。

Discussion