타입 레벨 프로그래밍은 TypeScript의 타입 시스템을 프로그래밍 언어처럼 사용해서 복잡한 타입을 계산하는 것입니다.

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]

튜플 뒤집기

TYPESCRIPT
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]

튜플 합치기

TYPESCRIPT
type Concat<A extends readonly any[], B extends readonly any[]> = [...A, ...B];
type C = Concat<[1, 2], [3, 4]>; // [1, 2, 3, 4]

패턴 매칭

infer와 조건부 타입으로 패턴 매칭을 구현합니다.

함수 타입 분해

TYPESCRIPT
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 풀기

TYPESCRIPT
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

문자열 파싱

TYPESCRIPT
// 문자열을 구분자로 분할
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

TYPESCRIPT
type DeepReadonly<T> =
  T extends Function ? T :
  T extends object ? { readonly [K in keyof T]: DeepReadonly<T[K]> } :
  T;

튜플에서 특정 타입 필터링

TYPESCRIPT
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

TYPESCRIPT
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]

타입 레벨 산술 (고급)

TYPESCRIPT
// 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단계). 깊이가 초과하면 에러가 발생합니다.

TYPESCRIPT
// ⚠️ 너무 깊은 재귀는 에러
type TooDeep<N extends number> =
  N extends 0 ? 'done' : TooDeep<Subtract<N, 1>>;
// Type instantiation is excessively deep

정리

  • TypeScript 타입 시스템은 튜링 완전하며 프로그래밍 언어처럼 사용할 수 있다
  • 튜플 조작(Head, Tail, Reverse, Filter)으로 데이터 구조를 타입 레벨에서 처리한다
  • infer로 패턴 매칭, 조건부 타입으로 분기, 재귀로 반복을 구현한다
  • 타입 레벨 산술은 튜플 길이를 이용한 트릭이다
  • 재귀 깊이 제한이 있으므로 과도한 재귀는 피해야 한다
댓글 로딩 중...