mcpField
F
functional-constructs-types

functional-constructs-types

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

functional-constructs-types について

map、filter、reduce などの関数型構文が型推論にどう役立つかを解説する TypeScript スキルです。データ変換を行い、関数型メソッドとループを使い分ける開発者向けです。

主な機能

  1. 01map、filter、reduce の優先
  2. 02チェーンでの型推論の維持
  3. 03コレクションの変換と集約
  4. 04filter の型ガードによる型の絞り込み
  5. 05ループが適する場面の判断

活用シーン

  1. 01配列構築ループの置き換え
  2. 02ネストした配列の変換
  3. 03コレクション内のユニオン型の絞り込み
  4. 04性能重視コードでの手法選択

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/marius-townhouse/effective-typescript-skills --skill functional-constructs-types

インストール後、Claude Code で/functional-constructs-typesを入力して読み込まれたか確認します。

詳細

型の流れを助ける関数型の構文とライブラリを使う

概要

関数型の構文(map、filter、reduce)は、命令型のループより TypeScript との相性が優れています。

TypeScript の型推論は、関数型の構文で特に効果を発揮します。関数型の構文は中間型を生成し、それらは自然に受け渡されます。一方、ループでは型を手動で追跡する必要があります。

このスキルを使う場面

  • for ループで配列を構築するとき
  • データ構造を変換するとき
  • 命令型コードで型がうまく受け渡されないとき
  • ループと関数型メソッドのどちらを使うか選ぶとき

絶対的なルール

Prefer map, filter, and reduce over for loops.
Types flow naturally through functional chains.

覚えておきましょう:

  • 関数型メソッドは型付きの値を返します
  • ループでは型を手動で管理する必要があります
  • メソッドチェーンでは型のコンテキストが維持されます
  • Lodash などのライブラリは優れた型サポートを備えています

検出:ループによる型の問題

// Loop: type must be declared or evolves
const result: string[] = [];
for (const item of items) {
  result.push(item.name);
}

// What if you forget the annotation?
const result = [];  // any[]
for (const item of items) {
  result.push(item.name);
}
result
// ^? any[] - type information lost

関数型による解決策

const result = items.map(item => item.name);
// ^? string[] - type inferred automatically

TypeScript は、入力型とマッピング関数から出力型を推論します。

チェーン内での型の流れ

const namesOfAdults = people
  .filter(p => p.age >= 18)
  // ^? Person[]
  .map(p => p.name)
  // ^? string[]
  .sort()
  // ^? string[]
  .join(', ');
  // ^? string

各ステップには明確に定義された型があり、TypeScript がそれを追跡します。

よくある変換

map:各要素を変換する
const numbers = [1, 2, 3];
const doubled = numbers.map(n => n * 2);
// ^? number[]

const users = [{ name: 'Alice', age: 30 }];
const names = users.map(u => u.name);
// ^? string[]
filter:条件に一致する要素を残す
const numbers = [1, 2, 3, 4, 5];
const evens = numbers.filter(n => n % 2 === 0);
// ^? number[]

// With type guard for narrowing
const mixed: (string | number)[] = [1, 'a', 2, 'b'];
const strings = mixed.filter((x): x is string => typeof x === 'string');
// ^? string[]
reduce:単一の値に集約する
const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce((acc, n) => acc + n, 0);
// ^? number

const grouped = items.reduce((acc, item) => {
  const key = item.category;
  acc[key] = acc[key] || [];
  acc[key].push(item);
  return acc;
}, {} as Record<string, Item[]>);
// Note: reduce sometimes needs type hints
flatMap:マッピングして平坦化する
const nested = [[1, 2], [3, 4], [5]];
const flat = nested.flatMap(arr => arr);
// ^? number[]

const sentences = ['Hello world', 'TypeScript rocks'];
const words = sentences.flatMap(s => s.split(' '));
// ^? string[]

オブジェクトの変換

Object.entries / Object.fromEntries
const obj = { a: 1, b: 2, c: 3 };

// Transform values
const doubled = Object.fromEntries(
  Object.entries(obj).map(([k, v]) => [k, v * 2])
);
// ^? { [k: string]: number }

// Filter entries
const filtered = Object.fromEntries(
  Object.entries(obj).filter(([k, v]) => v > 1)
);
Record の変換
type Input = Record<string, number>;
type Output = Record<string, string>;

const input: Input = { a: 1, b: 2 };
const output: Output = Object.fromEntries(
  Object.entries(input).map(([k, v]) => [k, String(v)])
);

Lodash と型に優しいライブラリ

import _ from 'lodash';

const grouped = _.groupBy(users, 'department');
// ^? Dictionary<User[]>

const sorted = _.sortBy(users, ['lastName', 'firstName']);
// ^? User[]

const unique = _.uniqBy(users, 'id');
// ^? User[]

Lodash は優れた TypeScript サポートを備えています。

ループを使ってもよい場合

パフォーマンスが重要なコード
// Loop might be faster for very large arrays
let sum = 0;
for (let i = 0; i < numbers.length; i++) {
  sum += numbers[i];
}
早期終了
// find is functional, but loops can break early
function findFirst<T>(arr: T[], pred: (x: T) => boolean): T | undefined {
  for (const item of arr) {
    if (pred(item)) return item;
  }
  return undefined;
}
// Or just use: arr.find(pred)
複雑な変更
// Some algorithms are clearer with loops
function quickSort<T>(arr: T[]): T[] {
  // ... loop-based implementation
}

ループを関数型に変換する

// Before: loop with accumulator
const result: ProcessedItem[] = [];
for (const item of items) {
  if (item.isValid) {
    result.push(processItem(item));
  }
}

// After: filter + map
const result = items
  .filter(item => item.isValid)
  .map(item => processItem(item));

圧力への対処プロトコル

1. 「ループのほうが読みやすい」

主張:「for ループのほうが理解しやすい」

**回答:**関数型メソッドは意図を明確に表現します。map は変換、filter は選択、reduce は集約です。

**対応:**パターンを覚えましょう。すぐに自然に使えるようになります。

2. 「パフォーマンスが心配」

主張:「複数回の処理は遅くなる」

**回答:**ほとんどのデータ規模では、細かな最適化より明瞭さを優先すべきです。

**対応:**最適化の前にプロファイリングしましょう。ほとんどのコードではパフォーマンスが重要になることはありません。

危険信号 — 立ち止まって再検討する

  • const result = [] の後に、要素を追加するループが続く
  • ループを使う場合に限って型注釈が必要になる
  • ループ内で複雑な状態を追跡している
  • より具体的な型にすべき any[]

よくある正当化(すべて誤り)

言い訳実際
「ループのほうが簡単」関数型メソッドのほうが意図を明確に表します
「インデックスが必要」.map((item, i) => ...) でインデックスを取得できます
「複数回の処理は遅い」通常は問題になりません。まず測定しましょう

クイックリファレンス

// DON'T: Loop with manual type
const result: string[] = [];
for (const x of items) {
  result.push(x.name);
}

// DO: Functional with inferred type
const result = items.map(x => x.name);

// Filter + Map
const processed = items
  .filter(x => x.isValid)
  .map(x => transform(x));

// Type guard in filter
const strings = mixed.filter((x): x is string => typeof x === 'string');

// Reduce (with type hint when needed)
const grouped = items.reduce((acc, x) => ..., {} as GroupedType);

要点

関数型の構文を使うと、型が自然に流れます。

map、filter、reduce などのメソッドは、手動で注釈を付けなくても、適切な型の結果を生成します。変換を明確に表現でき、TypeScript の型推論との相性も優れています。ループは、明確な理由がある場合にのみ使いましょう。

参考資料

Dan Vanderkam 著「Effective TypeScript」の項目 26「型の流れを助ける関数型の構文とライブラリを使う」に基づいています。

コメント