functional-constructs-types について
map、filter、reduce などの関数型構文が型推論にどう役立つかを解説する TypeScript スキルです。データ変換を行い、関数型メソッドとループを使い分ける開発者向けです。
主な機能
- 01map、filter、reduce の優先
- 02チェーンでの型推論の維持
- 03コレクションの変換と集約
- 04filter の型ガードによる型の絞り込み
- 05ループが適する場面の判断
活用シーン
- 01配列構築ループの置き換え
- 02ネストした配列の変換
- 03コレクション内のユニオン型の絞り込み
- 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「型の流れを助ける関数型の構文とライブラリを使う」に基づいています。