【入門編】高階関数における戻り値の型推論:Genericsで型を伝播させる – TypeScript コア・型システムの基礎解析バイブル

TypeScriptで「関数を返す関数」を極める:ジェネリクスによる型伝播の魔術

こんにちは。TypeScriptの深淵へようこそ。
コードを書いているとき、「あれ、この関数を返すと型が`any`になっちゃうな…」と頭を抱えたことはありませんか?

TypeScriptは非常に賢い言語ですが、「関数を返す関数(高階関数)」という構造に直面すると、途端に型推論の糸が絡まってしまうことがあります。今日は、この絡まった糸を解きほぐし、型情報を完全に維持したまま関数を伝播させる「ジェネリクスの極意」を伝授します。

—

1. なぜ「型」が消えてしまうのか?

まずは、やってしまいがちな「型が死ぬ」パターンを見てみましょう。

function createMultiplier(factor: number) {
// ここで返す関数の型が「関数」という抽象的なものだけに限定されがち
return (num: number) => num factor;
}

const double = createMultiplier(2);
// doubleの型推論は成功するが、複雑なジェネリクスを使うと崩壊する

一見問題なさそうですが、例えば「引数に渡した型を、戻り値の関数の引数としてそのまま使いたい」といった場合、単純な定義ではTypeScriptは「どの型がどこに対応しているか」を追跡できなくなります。

—

2. ジェネリクスで「型のバケツリレー」を行う

型情報を失わないための鍵は、「ジェネリクスを関数の入り口と出口の両方に配置すること」です。

イメージとしては、「入り口で受け取った型のバケツを、関数の内部を通って、最後に戻り値の関数に手渡す」というバケツリレーを想像してください。

// Tという型パラメータを、「関数を作る関数」の引数で定義する
function createTransformer(initialValue: T) {
// 返される関数の中で、そのTを再利用する
return (transform: (val: T) => T): T => {
return transform(initialValue);
};
}

// 実行!
const transformString = createTransformer(“Hello”);

// transformStringの引数は、自動的に (val: string) => string と推論される
const result = transformString((s) => s.toUpperCase());

console.log(result); // “HELLO”

ここがポイント!

  • `createTransformer` で、この関数のスコープ全体に `T` という型を宣言しました。
  • `transform: (val: T) => T` とすることで、「最初に渡した型 `T` と同じ型を扱う関数しか受け付けない」という制約をコンパイラに伝えています。
  • これにより、TypeScriptは「あ、これは文字列専用の変換器だな」と瞬時に理解し、強力な補完を提供してくれるのです。

—

3. よくある落とし穴:推論の限界

初心者が陥りやすいのが、「型を明示しすぎて推論を殺す」ケースです。

// 悪い例:戻り値を具体的な型で固定してしまう
function badFactory(val: T): (n: number) => number {
// 戻り値の型を固定すると、Tの情報がどこかへ消えてしまう
return (n) => n + 1;
}

このように、戻り値の型を `(n: number) => number` と明示的に書いてしまうと、TypeScriptは「ああ、この関数は中身が何であれ、とにかくこの型で返すんだな」と納得してしまい、`T` との関係性を捨ててしまいます。

「推論はコンパイラに任せる」のが、TypeScriptマスターへの第一歩です。型定義はなるべく「制約」のために使い、推論を阻害する「固定」は避けるのが鉄則ですよ。

—

4. 現場で使える「型伝播」の設計思想

実際の現場では、これに「オプショナル引数」が絡むことがよくあります。

function createConfigurator(defaultConfig: T) {
// 戻り値の関数は、一部の設定を上書きできるかもしれない
return (override?: Partial): T => {
return { …defaultConfig, …override };
};
}

const config = createConfigurator({ port: 8080, host: ‘localhost’ });

// 補完が効く!portやhostが自動的に型として認識される
const myConfig = config({ port: 3000 });

`Partial` を使うことで、「設定の一部だけ変えられる」という柔軟性を保ちつつ、`T` の型情報を一切失わずに伝播させています。これが、大規模開発における「型安全な構成」の基本です。

—

最後に:型を「コードのドキュメント」にする

TypeScriptの型定義は、単なるエラーチェックの道具ではありません。「このコードがどうあるべきか」を未来の自分やチームメイトに伝える、最高のドキュメントです。

関数を返す関数を書くときは、常に「この型はどこから来て、どこへ行くのか?」を意識してみてください。ジェネリクスというバケツリレーを正しく設計できれば、あなたの書くTypeScriptコードは劇的に美しく、そして堅牢なものに変わります。

ここをクリアできれば、もうTypeScriptの基礎はバッチリです。自信を持って、次の深い領域へ進んでいきましょう!

何か詰まったら、いつでも戻ってきてくださいね。応援しています。

タイトルとURLをコピーしました