mcpField
V
variadic-tuple-types

variadic-tuple-types

github.com/marius-townhouse開発643 回インストール38 週間前に更新
リポジトリ

variadic-tuple-types について

可変個の引数を取る関数に型を付けたい TypeScript 開発者向けのスキルです。レストパラメーター、タプル型、ジェネリクスで引数型を保ち、合成やカリー化に応用します。

主な機能

  1. 01可変引数関数の精密な型付け
  2. 02タプルによる引数型の保持
  3. 03変換でのジェネリクス活用
  4. 04合成とカリー化へのタプル適用

活用シーン

  1. 01可変個の引数を取る関数の型付け
  2. 02型付き関数合成ユーティリティの構築
  3. 03型安全なカリー化の実装
  4. 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
});

プレッシャーに対処するための手順

可変長関数を型付けする場合:

  1. タプル制約を使う:string | number ではなく [string, number] を使います
  2. ジェネリクスで保持する:T extends any[] で正確な型を捕捉します
  3. 長さを考慮する:条件型にはタプルの長さを使います
  4. エッジケースをテストする:空のタプル、単一要素、多数の要素を確認します

危険信号

アンチパターン問題解決策
...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 パラメーターとタプル型を使って可変長関数をモデル化する

コメント