【入門編】引数に渡す「関数」の戻り値型を別の引数の型として利用する「依存型」のシミュレーション – TypeScript コア・型システムの基礎解析バイブル

こんにちは!フロントエンドからバックエンドまで、TypeScriptの荒波を日々一緒に航海しているシニアアーキテクトの私です。

今回は、TypeScriptの型システムを一段階上のレベルへと引き上げる、非常にエキサイティングなテーマをお届けします。

それが、「引数に渡す『関数』の戻り値型を、別の引数の型として利用する(依存型のシミュレーション)」というテクニックです。

「他の言語(例えばIdrisやAgdaなど)にある本格的な『依存型(Dependent Types)』って、TypeScriptでもできるの?」と思われるかもしれません。厳密な意味での依存型そのものではありませんが、TypeScriptの強力なジェネリクスと推論エンジンを巧みに組み合わせることで、「ある引数の結果によって、次の引数の型が動的に決まる」という極めて高い安全性と柔軟性を兼ね備えたコードを書くことができます。

ここをクリアすれば、TypeScriptの型システムの「本質」がグッと見えてきますよ。さあ、一緒にマスターしていきましょう!

—

1. なぜこの「型依存関係」が必要なのか?

まずは、私たちがよく直面する「ちょっともどかしい状況」からお話ししますね。

例えば、ある「データを取得して、それを加工する処理」を汎用的な関数として書きたいとします。イメージとしてはこんな感じです。

// 良くあるけれど、型がフワッとしている例
function processValue(fetcher: () => any, handler: (data: any) => void) {
const data = fetcher();
handler(data);
}

このコード、動くには動きますが、`any`のオンパレードで安全性ゼロですよね。「`fetcher`が返すデータの型」と「`handler`が受け取るデータの型」が完全に一致しているべきなのに、TypeScriptがそれをチェックしてくれません。

これを「ジェネリクス(総称型)」を使って、コンパイラに賢く推論させ、型を結びつけてあげましょう。

—

2. 基本的なアプローチ:ジェネリクスで引数同士をつなぐ

TypeScriptの型推論エンジンは、私たちが思う以上に優秀です。関数に渡された値から「型」をパズルピースのように自動で組み立ててくれます。

まずは、もっともシンプルで実用的な実装を見てみましょう。

/

  • データをフェッチする関数と、それを処理する関数を受け取るユーティリティ
  • [型の相関図]
  • fetcher: () => T ──(戻り値型Tを抽出)──> handler: (data: T) => void

/
function executeWithData(
fetcher: () => T,
handler: (data: T) => void
): void {
// 1. fetcherを実行してデータを取得(型は T)
const data = fetcher();

// 2. 取得したデータをそのままhandlerに渡す(引数は T であることが保証される)
handler(data);
}

// === 実際の使用例 ===

executeWithData(
// fetcher引数:ここでは文字列を返す関数を渡しています
() => {
return “TypeScriptの型システムは最高だ”;
},

// handler引数:上の戻り値型(string)が、自動的に data の型として推論されます!
(data) => {
// data は自動的に string 型として扱われるため、string用のメソッドが補完されます
console.log(data.toUpperCase());
}
);

ここで何が起きているのか?(コードの裏側)

1. `T` という未知の型の箱を用意する:
関数に `` と書くことで、「`fetcher`の戻り値の型を `T` と名付けよう」とコンパイラに伝えます。
2. 型を伝播(プロパゲーション)させる:
TypeScriptは、第1引数である `fetcher` の中身(`() => “…”`)を見た瞬間、「あ、この `T` は `string` だな」と確定させます。
3. 第2引数へ型を適用する:
第1引数で決定した `T`(今回は `string`)を、そのまま第2引数の `handler` の引数型 `(data: T)` に流し込みます。

結果として、開発者は明示的に型を書かなくても、コンパイラが完璧に型安全性を担保してくれるようになります。これが、TypeScriptにおける依存型シエンレーションの第一歩です。

—

3. もう一歩進む:複数の引数が複雑に絡み合うケース

実際の開発現場では、もう少し複雑なケースに出会います。例えば、「設定オブジェクト」を受け取り、その設定内容に応じた値を返す関数があったとしましょう。

ここでは、「ある引数の戻り値」を「別の引数の型パラメータ」として利用する、より高度なパターンを見てみます。

// 設定の型
interface Config {
mode: “json” | “text”;
}

/

  • 依存型シミュレーションの応用例

/
function processWithDependency(
// 第1引数:設定によって戻り値の型が変わるパーサー
parser: (config: Config) => TResult,

// 第2引数:第1引数の戻り値(TResult)を受け取るコールバック
onSuccess: (result: TResult) => void,

// 第3引数:エラー時に呼ばれるコールバック
onError: (error: unknown) => void
): void {
try {
const config: Config = { mode: “json” };
const result = parser(config);
onSuccess(result);
} catch (e) {
onError(e);
}
}

// === 使用例 ===
processWithDependency(
// parser
(cfg) => {
// ここではオブジェクトを返しているので、TResult は { parsed: boolean } と推論される
return { parsed: true, format: cfg.mode };
},
// onSuccess
(res) => {
// res の型は自動的に { parsed: boolean; format: “json” | “text” } になる!
console.log(res.format);
},
// onError
(err) => {
console.error(err);
}
);

このように、関数のシグネチャ内でジェネリクスを巧みに配置することで、まるで関数型言語の依存型のような「データの流れに沿った型制約」を作り出すことができます。

—

4. 陥りやすい文法エラーとアンチパターン

このテーマを学習する過程で、初学者の皆さんが必ずと言っていいほどハマる「罠」があります。ここで先回りしてクリアしておきましょう。

罠1:ジェネリクスの記述順序や位置を間違える

よくあるのが、関数名や引数の型定義で `T` のスコープ(有効範囲)を見失うパターンです。

// ❌ 誤った例:Tがどこにも定義されていない、またはスコープ外
function badExample(
fetcher: () => T, // エラー: 找不到名称 ‘T’ (Cannot find name ‘T’)
handler: (data: T) => void
) {
// …
}

対策: 必ず関数名の直後(またはアロー関数の場合は引数リストの前)に `` を置いて、型変数を宣言してください。

罠2:`any` や `unknown` で型推論を放棄してしまう

「型エラーが面倒だから」といって、次のように書いてしまうのはTypeScriptの恩恵をドブに捨てるようなものです。

// ❌ アンチパターン:型推論を殺している
function lazyExample(fetcher: () => any, handler: (data: any) => void) {
handler(fetcher());
}

対策: 面倒くさがらずに `` を使い、コンパイラにパチッと型が噛み合う気持ちよさを味方につけましょう。

—

5. 今回のまとめ:ここをクリアすれば基礎はバッチリ!

  • 依存型シミュレーションの本質:

TypeScriptのジェネリクス(``)を使うことで、ある引数(関数など)の型情報を別の引数へ動的に伝播させることができる。

  • メリット:

余計な型アサーション(`as`など)や `any` を排除し、コードの意図が明確な堅牢なインターフェースを作れる。

  • ポイント:

コンパイラの「型推論の方向」を意識し、データの流れと型の流れを一致させること。

ここをしっかりと理解できたあなたは、もう単なる「JavaScriptに型をつけている人」ではありません。「TypeScriptの型システムを巧みに操るアーキテクト」の第一歩を踏み出しています。

明日からのコーディングで、ぜひこのテクニックを使ってみてください。コードの美しさと安全性が劇的に変わるはずですよ!それではまた次回のテックブログでお会いしましょう。

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