こんにちは!TypeScriptの型システムの世界へようこそ。
日々フロントエンドからバックエンドまでコードを書いていると、「あ、この高階関数には、引数を1つだけ取る関数(コールバック)を渡してほしいのに、うっかり2つも3つも取る関数を渡されちゃったぞ……実行時になんだかバグるな」という経験、ありませんか?
JavaScriptの柔軟さゆえに、`Array.prototype.map`などに渡すコールバック関数が、本来予期していない引数(例えば、インデックス番号など)を受け取ってしまい、意図しない挙動を引き起こすのはあるあるですよね。
「引数が多すぎる関数を渡したら、コンパイルの段階(書いている最中)で赤くエラーを出して弾いてほしい!」
今回は、そんな願いをTypeScriptの型システムで鮮やかに叶える「関数のアリティ(引数の数)を型レベルで制限するテクニック」を、一緒に紐解いていきましょう。
ここをクリアすれば、あなたの書くライブラリや関数の型安全性は一段と跳ね上がりますよ。バッチリマスターしていきましょう!
—
1. アリティ(Arity)ってなんだろう?
まず言葉の定義から。プログラミングの世界で「アリティ(Arity)」とは、関数が受け取る引数の数のことです。
- 引数が1つの関数:アリティ1(Unary)
- 引数が2つの関数:アリティ2(Binary)
今回やりたいことは、高階関数(関数を受け取る関数)を作る時に、「ウチの関数に渡せるのは、引数が最大でも〇個までの関数だけだよ!」という制限を型で強制することです。
脳内イメージ図
[あなたの高階関数] ──(受け入れ可能な引数は「最大1つ」)──> [渡されたコールバック関数]
│
┌────────────────────────────────────────────────────────┘
▼
(x: number) => number ✅ セーフ!引数は1つ
(x: number, i: number) => … ❌ アウト!引数が2つなので型エラー!
—
2. まずは基本的な使い方を見てみよう
百聞は一見にしかず。まずは、「引数がちょうど1個の関数しか受け付けない高階関数」を実装してみましょう。
TypeScriptの型システムを巧みに操ることで、次のようなコードが書けます。
// — 1. 型の定義(マジックのタネ) —
// 関数の引数のタプル型(引数のリスト)を取り出す型ヘルパー
// Parameters
// 例: Parameters<(x: string, y: number) => void> は [string, number] になります
// 引数の数が「1個」であるかを判定する型
type IsUnary
Parameters
// — 2. 高階関数の実装 —
/
- 渡された関数が「引数1つ」のときだけ実行を許可する関数
/
function processWithUnary
callback: IsUnary
): ReturnType
// 実際にはとりあえずダミーの数値を渡して実行してみる例
return callback(42 as any);
}
// — 3. 実際の使用例 —
// ✅ 正しい例:引数が1つの関数
const goodFunction = (x: number) => {
return x 2;
};
processWithUnary(goodFunction); // コンパイル成功!
// ❌ エントリーエラーになる例:引数が2つの関数
const badFunction = (x: number, index: number) => {
return x + index;
};
processWithUnary(badFunction);
// 💥 ほら、ここで型エラーが起きます!
// 「Type ‘(…x: [number, number]) => number’ is not assignable to type ‘never’.」
すごいですね!`badFunction`を渡した瞬間、TypeScriptが「おいおい、ここは引数1つの関数専用だぞ」とコンパイルエラーを出して教えてくれます。
—
3. コードの意味をじっくり解剖する
「なんでこれでうまく動くの?」という疑問が湧いたはず。先輩エンジニアとして、このコードの心臓部を分かりやすく分解して解説しますね。
キーワードは `Parameters` と `extends [any]`
type IsUnary
Parameters
1. `Parameters
渡された関数 `T` の引数を「配列の形(タプル型)」で抽出します。例えば `(a: string, b: number) => void` なら `[string, number]` です。
2. `extends [any]` の条件分岐(Conditional Types):
「抽出した引数のタプル型が、要素がちょうど1つの配列 (`[any]`) にマッチするか?」を判定しています。
3. 三項演算子 `? T : never`:
もし条件に一致(引数が1つ)していれば、そのまま元の関数型 `T` を返します。
もし一致しなければ(引数が0個、または2個以上なら)、TypeScriptの世界で「存在してはならない型」を表す `never` に化けさせます。
関数を受け取る引数の型が `never` に強制されるため、TypeScriptは「そんな関数は割り当てられないよ!」と怒り出すという仕組みです。型パズル的で美しいアプローチですよね。
—
4. 陥りやすい文法エラーと注意点
このテクニックを使うとき、初学者がよくハマる「罠」がいくつかあります。あらかじめ知っておけば怖くありません。
罠1: `(…args: any[])` との混同
関数を定義するときに、つい `(…args: any[]) => any` と可変長引数で受け取りがちですが、これをしてしまうと引数の数が不定になってしまい、うまくアリティを制限できません。
制限したい場合は、必ず「特定の引数の数」にマッチさせる条件型を挟み込む必要があります。
罠2: オプショナル引数(`?`)の扱いに注意
例えば、次のような関数はどうでしょうか?
const optionalArgFunc = (x?: number) => { … }
この関数、引数の定義は1つですが、呼び出す側は `optionalArgFunc()` と引数ゼロで呼ぶこともできます。
厳密なアリティ制限(「絶対に引数が1つでなければならない」)をしたい場合、オプショナル引数やデフォルト引数をどう扱うかも設計のポイントになります。現場の要件に合わせて、`[any]` だけでなく `[any, …any[]]` (最低1つ以上)にするなど、タプルのパターンを調整してみてください。
—
5. まとめ
いかがでしたでしょうか? 今回は、関数における型定義の応用編として、引数の数(Arity)を型レベルで制限するテクニックを解説しました。
- アリティとは引数の数のこと。
- `Parameters
` で引数を抽出し、条件型(`extends`)と `never` を組み合わせることで、特定の引数の数を持つ関数だけを厳格に弾き出せる。 - これにより、高階関数に意図しない引数を持つコールバックが渡るのをコンパイル時に完全に防ぐことができる。
ここまで理解できれば、TypeScriptの型システムの「条件分岐」や「高度な関数型」の基本はもうバッチリマスターしています!自信を持って、日々の開発やライブラリ設計に応用してみてくださいね。
それでは、快適なTypeScriptライフを!