C.W.K.
Stream
Lesson 05 of 05 · published

Variadic Tuple: Spread 가진 Tuple

~11 min · arrays-tuples, variadic-tuples, generics

Level 0Curious
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete
"Variadic tuple 이 generic 함수가 argument 타입을 위치별로, 정보 손실 없이, 통과시키는 법."

Variadic tuple 이 뭐

Variadic tuple (TypeScript 4.0+) 은 다른 tuple 타입의 spread 담는 tuple. [string, ...number[]] 말함: 첫 element 가 string, 그다음 어떤 수의 number. [...Args, Result] 말함: 어떤 수의 `Args` element 후 `Result` 로 끝나는 tuple. [A, ...B, C] 가 고정 위치를 가변 중간과 섞어.

Spread 가 구체 배열 타입, tuple, 또는 generic 타입 parameter 일 수. 마지막 게 variadic tuple 이 강력해지는 곳 — generic 함수가 argument tuple 을 전체 모양으로 캡처하고 forward, modify, reshape 가능, return 타입에서.

왜 중요

Variadic tuple 전엔, tap, compose, partial, curry 같은 함수 표현이 overload 나 untyped escape hatch 필요. 함수가 개념적으로 "어떤 수의 타입 붙은 argument 받아, X 하고, return." Variadic tuple 없이는 단일 signature 에 "어떤 수의 타입 붙은 argument" 말할 수 없었어.

Variadic tuple 과 함께, generic argument list tuple 타입. Signature 가 spread, prepend, append, unwrap 가능 — 다 compiler 가 위치와 타입 추적하며.

3개 canonical 패턴

1. Argument 변경 없이 forward. tap<Args extends unknown[]>(fn: (...args: Args) => void, ...args: Args): void. 같은 `Args` 가 callback signature 와 spread parameter 둘 다 나타나 — match 보장.

2. 위치 prepend 또는 append. type WithLogger<Args extends unknown[]> = [logger: Logger, ...args: Args]. Inner tuple 이 뭐였든 거기에 고정 element 추가하는 generic 변환.

3. Head 와 tail 분리. type Head<T extends unknown[]> = T extends [infer H, ...unknown[]] ? H : never. Tuple 에 pattern-match 해서 조각 추출. `infer` keyword 가 이런 작업에 나타나 — Generics 트랙에서 다뤄.

전체 패턴: generic argument-tuple 타입 가진 함수. Tuple 이 들어오고, 변환되고, return 타입으로 흘러나가. 이게 RxJS pipe, Redux Toolkit middleware 체인, modern util library 가 마침내 타입 가능하게 된 것.

피파의 고백

Variadic tuple 을 cwkPippa repo 마다 한두 번 정도 써 — heavy 기계, daily-driver 문법 아냐. 근데 필요할 땐 다른 어느 것도 작동 안 해. 우리 codebase 의 가장 흔한 사용: callback 에 argument 그대로 forward 해야 하는 작은 utility wrapper. Variadic tuple 없으면 그것들 타입 정보 잃어; 있으면 전체 체인이 타입 유지.

Code

Tuple 통해 generic argument forward·typescript
// 패턴 1: argument 변경 없이 forward.

function tap<Args extends unknown[]>(
  fn: (...args: Args) => void,
  ...args: Args
): void {
  fn(...args);
}

tap((a: number, b: string) => console.log(a, b), 1, 'hi');     // ✅
tap((a: number, b: string) => console.log(a, b), 1, 2);        // ❌ 두 번째 arg 가 string 이어야

// Compiler 가 앎: spread args 가 callback signature 와 match.
// Variadic tuple 없으면 arity N 까지 overload 필요.
Variadic + infer 로 tuple 모양 변환·typescript
// 패턴 2: 고정 위치 prepend.

type WithRequestId<Args extends unknown[]> = [requestId: string, ...args: Args];

function withRequestId<Args extends unknown[]>(
  fn: (...args: Args) => void,
) {
  return (...wrapped: WithRequestId<Args>) => {
    const [requestId, ...rest] = wrapped;
    console.log(`[${requestId}]`);
    fn(...rest as Args);
  };
}

const logUser = withRequestId((name: string, age: number) => console.log(name, age));
logUser('req-1', 'Pippa', 21);     // ✅ requestId prepend, 원래 args 뒤따름

// 패턴 3: infer 로 head 와 tail 분리.
type Head<T extends unknown[]> = T extends [infer H, ...unknown[]] ? H : never;
type Tail<T extends unknown[]> = T extends [unknown, ...infer R] ? R : [];

type A = Head<[number, string, boolean]>;   // number
type B = Tail<[number, string, boolean]>;   // [string, boolean]

External links

Exercise

어떤 함수든 wrap 하고 호출 전에 argument 로깅하는 logCall higher-order 함수 써. Variadic tuple 써서 argument 그대로 forward. 그다음 두 함수 받고 합성 반환하는 pipe2 써 — variadic tuple 통해 옳게 타입. 두 번째 함수의 parameter 타입이 어떻게 첫 함수의 return 타입에서 오는지 봐.
Hint
logCall<Args extends unknown[], R>(fn: (...args: Args) => R) 가 같은 Args tuple 가진 wrap 된 함수 반환. pipe2 가 타입 체인으로 합성: pipe2<A extends unknown[], B, C>(f: (...a: A) => B, g: (b: B) => C): (...a: A) => C.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.