variadic-tuple-types について
可変個の引数を取る関数に型を付けたい TypeScript 開発者向けのスキルです。レストパラメーター、タプル型、ジェネリクスで引数型を保ち、合成やカリー化に応用します。
主な機能
- 01可変引数関数の精密な型付け
- 02タプルによる引数型の保持
- 03変換でのジェネリクス活用
- 04合成とカリー化へのタプル適用
活用シーン
- 01可変個の引数を取る関数の型付け
- 02型付き関数合成ユーティリティの構築
- 03型安全なカリー化の実装
- 04タプルによるイベント引数のモデル化
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ marius-townhouse/ effective-typescript-skills --skill variadic-tuple-typesインストール後、Claude Code で/variadic-tuple-typesを入力して読み込まれたか確認します。
詳細
Rest パラメーターとタプル型を使って可変長関数をモデル化する
概要
可変個の引数を受け取る関数である可変長関数は、JavaScript でよく使われます。TypeScript のタプル型と rest パラメーターを使えば、引数の数と型を保持しながら、これらの関数を正確に型付けできます。これにより、型付き関数合成やジェネリックなパイプラインなど、強力なパターンを実現できます。
rest パラメーターとタプル型を組み合わせる方法を理解すると、柔軟な API を正確に型付けできるようになります。
このスキルを使う場面
- 関数が型付きの可変個の引数を受け取る
- 変換を通じてタプルの長さを保持する必要がある
- ジェネリックな関数合成ユーティリティを構築する
- 特定の制約を持つ rest パラメーターを型付けする
- 型付き関数パイプラインを作成する
絶対原則
rest パラメーターとタプル型を使って、可変長関数を正確に型付けします。ジェネリクスと組み合わせて、変換を通じて引数の型を保持します。
検出方法
次のような型付けされていないパターンに注意してください。
// RED FLAGS - Untyped variadic functions
function logAll(...args: any[]): void; // Lost type information
function compose(...fns: Function[]): Function; // No type safety
function curry(fn: Function): Function; // Arguments not tracked
タプルを使った基本的な rest パラメーター
// Preserve exact argument types
function logAll<T extends any[]>(...args: T): void {
console.log(args);
}
logAll(1, 'hello', true);
// T is inferred as [number, string, boolean]
// Constrain the tuple
type StringNumberPair = [string, number];
function formatPair(...pair: StringNumberPair): string {
return `${pair[0]}: ${pair[1]}`;
}
formatPair('score', 100); // OK
formatPair('score'); // Error: missing number
formatPair('score', 100, 'extra'); // Error: too many arguments
ジェネリックな可変長関数
// Preserve types through transformation
function tail<T extends any[]>(
head: T[0],
...rest: T extends [any, ...infer R] ? R : never
): T {
return [head, ...rest] as T;
}
const result = tail(1, 'a', true);
// result: [number, string, boolean]
関数合成
// Compose functions with preserved types
type Fn = (...args: any[]) => any;
function compose<T extends Fn[]>(
...fns: T
): (...args: Parameters<T[0]>) => ReturnType<T[number]> {
return (arg) => fns.reduceRight((acc, fn) => fn(acc), arg);
}
const add1 = (x: number) => x + 1;
const double = (x: number) => x * 2;
const toString = (x: number) => String(x);
const composed = compose(toString, double, add1);
// (x: number) => string
const result = composed(5); // "12"
タプル型を使ったカリー化
// Type-safe currying
type Curry<T extends any[], R> = T extends [infer First, ...infer Rest]
? (arg: First) => Rest extends [] ? R : Curry<Rest, R>
: () => R;
function curry<T extends any[], R>(
fn: (...args: T) => R
): Curry<T, R> {
return ((arg: any) => {
if (arguments.length >= fn.length) {
return fn(...arguments);
}
return curry(fn.bind(null, arg));
}) as Curry<T, R>;
}
const sum3 = (a: number, b: number, c: number) => a + b + c;
const curried = curry(sum3);
const result = curried(1)(2)(3); // 6
// Each step is correctly typed!
実例:イベントエミッター
type EventMap = {
click: [x: number, y: number];
keypress: [key: string];
load: [];
};
class TypedEmitter<Events extends Record<string, any[]>> {
emit<K extends keyof Events>(
event: K,
...args: Events[K]
): void {
// Implementation
}
on<K extends keyof Events>(
event: K,
handler: (...args: Events[K]) => void
): void {
// Implementation
}
}
const emitter = new TypedEmitter<EventMap>();
emitter.emit('click', 100, 200); // OK
emitter.emit('click', 100); // Error: missing y
emitter.on('keypress', (key) => {
console.log(key); // key is string
});
プレッシャーに対処するための手順
可変長関数を型付けする場合:
- タプル制約を使う:
string | numberではなく[string, number]を使います - ジェネリクスで保持する:
T extends any[]で正確な型を捕捉します - 長さを考慮する:条件型にはタプルの長さを使います
- エッジケースをテストする:空のタプル、単一要素、多数の要素を確認します
危険信号
| アンチパターン | 問題 | 解決策 |
|---|---|---|
...args: any[] | 型情報が失われます | T extends any[] |
Function 型 | パラメーター型と戻り値型がありません | ジェネリックな関数型 |
| タプルではなく配列を使う | 長さの情報が失われます | タプル型 |
よくある正当化
「自分のユースケースには複雑すぎます」
実際には:まず T extends any[] から始め、必要な場合にのみ複雑さを加えます。
「ユーザーは引数を配列として渡せます」
実際には:rest パラメーターのほうが使いやすいです。最適な開発者体験のために、正しく型付けしてください。
「オーバーロードを使えばよいでしょう」
実際には:タプルなら一般的なケースに対応できます。オーバーロードでは手作業で列挙する必要があります。
クイックリファレンス
| パターン | 構文 | 用途 |
|---|---|---|
| 引数を捕捉する | T extends any[] | 引数の型を保持する |
| 長さを制約する | [A, B, C] | 引数の数を固定する |
| 最小の長さ | [A, B, ...C[]] | 2 個以上の引数を指定する |
| 変換する | Parameters<T[0]> | パラメーター型を抽出する |
要点
タプル型を使った rest パラメーターにより、可変長関数を正確に型付けできます。ジェネリクスを使って変換を通じて引数の型を保持すれば、型付き関数合成のような強力なパターンを実現できます。
参考資料
- Dan Vanderkam 著『Effective TypeScript』第 2 版
- 項目 62:Rest パラメーターとタプル型を使って可変長関数をモデル化する
コメント
Markdown 対応