[TypeScript] "T extends U ? X : Y"에 대한 고찰
TL;DR
- 타입은 가능한 값의 집합이다.
- 서브 타입과 슈퍼 타입을 집합의 관계로 설명할 수 있다.
- 조건부 타입은 분배법칙을 허용한다.
타입은 집합
타입은 가능한 값의 집합이다.
타입은 가능한 값의 집합? 직관적으로 한 번에 이해가 잘 되지 않는다. 예시를 통해 타입과 집합 관계를 이해해보자.
never
타입은 어떠한 값도 할당할 수 없는 타입이다. 어떠한 값도 할당할 수 없기 때문에 공집합이라고 말할 수 있으며, never = {}
라고 표기할 수 있다.
예를 하나 더 들어보자. number
타입에는 정수, 소수를 할당할 수 있다. 그러면 number = {정수, 소수}
라고 표기할 수 있다.
서브타입과 슈퍼 타입
집합 A가 집합 B의 부분 집합이면 A ⊂ B
라고 표기할 수 있다. 두 집합이 서로 같을 때에도 부분 집합 관계라고 한다. 타입은 집합이라고 했으니, 집합의 개념을 타입에 적용할 수 있다. 타입스크립트에서는 타입 간 관계를 설명하는 서브 타입과 슈퍼 타입의 개념이 존재한다. 타입 A와 B가 A ⊂ B
관계를 가질 때, A는 B의 서브타입이고 B는 A의 슈퍼타입이라고 할 수 있다. 단, 수학의 부분 집합 개념과 달리 A와 B가 서로 달라야 한다. 이 부분을 명확하기 위해 아래와 같은 표기법으로 타입 간의 관계를 이해해보자. (타입스크립트 프로그래밍 책에서 사용하는 표기법이다.)
A < B
: A는 B의 서브 타입이다. (B는 A의 슈퍼 타입이다.)A <: B
: A는 B와 같거나, B의 서브 타입이다. (B는 A와 같거나 A의 슈퍼 타입이다.)
예시)
- 1은 number의 서브 타입 이다. =>
1 < number
- "Hello World"는 stirng의 서브 타입이다. =>
"Hello World" < string
- tuple은 array의 서브 타입이다.
tuple < array
조건부 타입
T extends U ? X : Y
조건부 타입은 타입스크립트 2.8 버전부터 사용할 수 있는 문법이다. 사용 방법은 T extends U ? X : Y
로 표기하며, T <: U
관계이면 X
를, 아니면 Y
타입을 반환한다.
조건부 타입과 분배 법칙
T extends U ? X : Y
에서 T가 유니온 타입이면 분배 법칙이 적용된다. (핸드북 참고)
예로, 간단한 문제를 풀어보자.
T에서 U에 할당할 수 있는 타입을 제외하는 내장 제네릭 MyExclude<T, U>를 이를 사용하지 않고 구현하세요. (출처: https://github.com/type-challenges/type-challenges/blob/main/questions/00043-easy-exclude/README.ko.md)
문제는 타입 T와 U의 차집합을 구하는 것이다. 타입 T가 "a" | "b" | "c"
이고 , U가 "a"
라고 생각해보자. T는 유니온 타입이니 조건부 타입에서 분배 법칙이 사용된다. T의 각 타입이 U에 할당될 수 있는지 확인하고 할당될 수 있다면 공집합 인 never
타입을, 할당될 수 없다면 해당 타입을 반환해주면 된다. 이를 정리하면 type MyExclude<T, U> = T extends U ? never : T;
라고 정의할 수 있다.
/*
e.g)
Set T is "a" | "b" | "c" and U is "a"
= T extends U ? never : T
= ("a" extends "a" ? never : "a")
| ("b" extends "a" ? never : "b")
| ("c" extends "a" ? never : "c")
= never | "b" | "c"
= "b" | "c"
*/
type MyExclude<T, U> = T extends U ? never : T;