타입 레벨 프로그래밍 — 재귀, 패턴 매칭, 튜플 조작
타입 레벨 프로그래밍은 TypeScript의 타입 시스템을 프로그래밍 언어처럼 사용해서 복잡한 타입을 계산하는 것입니다.
TypeScript의 타입 시스템은 ** 튜링 완전 **합니다. 조건부 타입, 재귀, 튜플 조작을 조합하면 타입 레벨에서 거의 모든 연산을 구현할 수 있습니다.
튜플 조작
기본 연산
// 길이
type Length<T extends readonly any[]> = T['length'];
type L = Length<[1, 2, 3]>; // 3
// 첫 번째 요소
type Head<T extends readonly any[]> = T extends [infer H, ...any[]] ? H : never;
type H = Head<[1, 2, 3]>; // 1
// 나머지 요소
type Tail<T extends readonly any[]> = T extends [any, ...infer R] ? R : [];
type T = Tail<[1, 2, 3]>; // [2, 3]
// 마지막 요소
type Last<T extends readonly any[]> = T extends [...any[], infer L] ? L : never;
type La = Last<[1, 2, 3]>; // 3
// 앞에 추가
type Prepend<T extends readonly any[], E> = [E, ...T];
type P = Prepend<[2, 3], 1>; // [1, 2, 3]
// 뒤에 추가
type Append<T extends readonly any[], E> = [...T, E];
type A = Append<[1, 2], 3>; // [1, 2, 3]
튜플 뒤집기
type Reverse<T extends readonly any[]> =
T extends [infer H, ...infer R]
? [...Reverse<R>, H]
: [];
type Rev = Reverse<[1, 2, 3, 4]>; // [4, 3, 2, 1]
튜플 합치기
type Concat<A extends readonly any[], B extends readonly any[]> = [...A, ...B];
type C = Concat<[1, 2], [3, 4]>; // [1, 2, 3, 4]
패턴 매칭
infer와 조건부 타입으로 패턴 매칭을 구현합니다.
함수 타입 분해
type FnArgs<T> = T extends (...args: infer A) => any ? A : never;
type FnReturn<T> = T extends (...args: any) => infer R ? R : never;
type Args = FnArgs<(a: string, b: number) => boolean>; // [a: string, b: number]
type Ret = FnReturn<(a: string) => boolean>; // boolean
Promise 풀기
type Awaited<T> =
T extends null | undefined ? T :
T extends object & { then(onfulfilled: infer F): any }
? F extends (value: infer V, ...args: any) => any
? Awaited<V>
: never
: T;
type A = Awaited<Promise<Promise<string>>>; // string
문자열 파싱
// 문자열을 구분자로 분할
type Split<
S extends string,
D extends string
> = S extends `${infer Head}${D}${infer Tail}`
? [Head, ...Split<Tail, D>]
: [S];
type Parts = Split<'a.b.c', '.'>; // ['a', 'b', 'c']
type Words = Split<'hello world foo', ' '>; // ['hello', 'world', 'foo']
재귀 타입
깊은 Readonly
type DeepReadonly<T> =
T extends Function ? T :
T extends object ? { readonly [K in keyof T]: DeepReadonly<T[K]> } :
T;
튜플에서 특정 타입 필터링
type Filter<T extends readonly any[], U> =
T extends [infer H, ...infer R]
? H extends U
? [H, ...Filter<R, U>]
: Filter<R, U>
: [];
type OnlyStrings = Filter<[1, 'a', 2, 'b', true], string>;
// ['a', 'b']
타입 레벨 FlatMap
type FlatMap<T extends readonly any[]> =
T extends [infer H, ...infer R]
? H extends readonly any[]
? [...H, ...FlatMap<R>]
: [H, ...FlatMap<R>]
: [];
type Flat = FlatMap<[[1, 2], [3, 4], 5]>;
// [1, 2, 3, 4, 5]
타입 레벨 산술 (고급)
// N개의 요소를 가진 튜플 생성
type BuildTuple<N extends number, T extends any[] = []> =
T['length'] extends N ? T : BuildTuple<N, [...T, unknown]>;
// 덧셈
type Add<A extends number, B extends number> =
[...BuildTuple<A>, ...BuildTuple<B>]['length'] & number;
type Sum = Add<13, 29>; // 42
// 비교
type GreaterThan<A extends number, B extends number> =
BuildTuple<A> extends [...BuildTuple<B>, ...infer Rest]
? Rest extends [any, ...any[]]
? true
: false
: false;
type GT = GreaterThan<5, 3>; // true
type LT = GreaterThan<2, 5>; // false
재귀 깊이 제한
TypeScript는 재귀 타입의 깊이를 제한합니다 (약 1000단계). 깊이가 초과하면 에러가 발생합니다.
// ⚠️ 너무 깊은 재귀는 에러
type TooDeep<N extends number> =
N extends 0 ? 'done' : TooDeep<Subtract<N, 1>>;
// Type instantiation is excessively deep
정리
- TypeScript 타입 시스템은 튜링 완전하며 프로그래밍 언어처럼 사용할 수 있다
- 튜플 조작(Head, Tail, Reverse, Filter)으로 데이터 구조를 타입 레벨에서 처리한다
infer로 패턴 매칭, 조건부 타입으로 분기, 재귀로 반복을 구현한다- 타입 레벨 산술은 튜플 길이를 이용한 트릭이다
- 재귀 깊이 제한이 있으므로 과도한 재귀는 피해야 한다
댓글 로딩 중...