08 모듈과 네임스페이스
09 실제 프로젝트에서 사용하기
10 유용한 라이브러리 소개

6.4 집합으로서의 타입

프로그래밍의 타입이 수학의 집합과 공유하는 성질, 그리고 타입을 집합으로 바라보는 관점에 대해 배운다.

타입스크립트 문법으로부터 잠시 쉬어가는 시간을 갖자. 이 장에서 다루는 내용은 언뜻 보아선 타입스크립트와 동떨어진 듯 보일 수 있다. 하지만 일단 이해하고 나면 타입스크립트 뿐만 아니라 다른 여러 정적 타입 언어들을 이해하는 데에 큰 도움이 될 것이다.

타입은 집합이다

프로그래밍 언어에서의 타입이란 뭘까? 비슷한 성격의 값들을 모아 놓은 무언가? 지켜야 할 규칙? 우리는 타입에 대해 자주 이야기하지만 정작 타입라는 개념이 갖는 함의에 대해서는 잘 이야기하지 않는다. 타입을 이해하기 위한 다양한 수단 중 상대적으로 쉽고 직관적인 개념이 있다. 바로 집합이다.

프로그래밍에서의 타입은 수학에서의 집합과 매우 많은 특징을 공유한다. 보다 과격하게 표현하자면, 타입은 집합이다. 실제로 타입스크립트의 값과 타입의 관계는 집합의 원소와 집합의 관계와 굉장히 유사하다. 타입(집합)이란 결국 값(원소)들의 모임이 아닌가!

어떤 값 value가 타입 T에 속한다는 사실을 원소 value가 집합 T에 속한다(value ∈ T)는 사실에 대응해 생각해보자. 예를 들어, true 라는 원소가 Boolean 이란 집합에 속한다는 식으로 말이다. 이런 관점으로 타입스크립트의 내장 타입 numbertype Binary = 0 | 1 두 타입을 바라보면 아래와 같은 대응 관계들이 성립한다.

값과 타입

원소와 집합

0은 타입 number에 속한다.

원소 0이 집합 number에 속한다.

값은 여러 타입에 속할 수 있다. 예를 들어, 0numberBinary 두 타입에 모두 속한다.

한 원소는 여러 집합에 속할 수 있다.

Binary 타입의 모든 값(0, 1)은 number 타입의 값이기도 하다. 즉 Binarynumber의 서브타입이다.

Binary의 모든 원소는 number의 원소이기도 하다. 즉 Binarynumber의 부분집합이다.

이미 존재하는 두 타입을 이용해 더 복잡한 타입을 만들 수 있다. { binary: Binary, num: number }

두 집합을 사용해 새로운 집합을 정의할 수 있다. { (binary, num) | binary ∈ Binary, num ∈ number }

어떤 값도 가질 수 없는 타입 never가 존재한다. never는 모든 타입의 서브타입이다.

원소가 없는 공집합 Ø가 존재한다. Ø는 모든 집합의 부분집합이다.

이 내용을 보다 일반적으로 확장해보자.

  • 어떤 프로그래밍 언어 L로 쓰인 프로그램이 가질 수 있는 모든 값의 집합을 V라 부르자.

  • 이때, V의 원소 중 특정한 조건을 만족하는 값을 모아 이 집합을 L에서의 타입 T라고 정의할 수 있다.

  • 이렇게 타입을 집합으로서 바라볼 때, 아래와 같은 대응 관계가 성립한다.

원소와 집합

값과 타입

원소 x가 집합 S에 속한다. (x ∈ S)

xS 타입에 속한다. (혹은 S 타입을 값 x에 할당할 수 있다.)

한 원소는 여러 집합에 속할 수 있다.

한 값은 여러 타입에 속할 수 있다.

집합 T가 집합 S의 부분집합이다. (T ⊂ S)

타입 T가 타입 S의 서브타입이다.

조건 제시법을 이용해 기존에 존재하는 집합으로부터 새로운 집합을 만들어낼 수 있다. (S’ = { (x, y) | x ∈ S, y ∈ S })

기존 타입의 정의로부터 새로운 타입을 만들어낼 수 있다(객체 타입, 유니온 타입, ...)

모든 집합의 부분집합인 공집합 Ø이 존재한다.

모든 타입의 서브타입인 바닥 타입bottom type이 존재한다 (혹은 존재할 수 있다).

이런 대응 관계에서 알 수 있듯이, 프로그래밍의 타입은 수학의 집합에, 값은 원소에 대응한다. 타입은 값의 모음, 즉 집합인 것이다.

유니온 타입은 합집합이다

위에서 타입이 집합과 매우 유사한 개념임을 알 수 있었다. 이제 이런 관점에서 유니온 타입을 한 번 바라보자.

type Union = A | B;

위 코드가 갖는 의미는 다음과 같이 풀어쓸 수 있다.

  • A 타입의 모든 값은 Union 타입의 값이다.

  • B 타입의 모든 값은 Union 타입의 값이다.

그리고 이는 집합의 관점에서 다음과 같이 해석할 수 있다.

  • 집합 A의 모든 원소는 집합 Union의 원소이다.

  • 집합 B의 모든 원소는 집합 Union의 원소이다.

친숙하게 느껴지지 않는가? 이는 곧 합집합의 정의이다. 즉, 유니온 타입 Union은 타입 시스템에서 합집합 A ∪ B 를 표현하는 수단이다! 유니온 타입이 합집합이라는 의미의 유니온(union)이라는 이름을 가진 건 우연이 아니다.

인터섹션 타입은 교집합이다

다음으로는 인터섹션 타입은 어떤 의미를 지닐지 생각해보자. 눈치 빠른 독자라면 벌써 눈치 챘을 것이다.

type Intersection = A & B;

위 코드를 풀어쓰면 다음과 같다.

  • Intersection 타입의 모든 값은 A 타입의 값이다.

  • Intersection 타입의 모든 값은 B 타입의 값이다.

마찬가지로 이를 집합의 관점에서 해석해보자.

  • 집합 Intersection의 모든 원소는 집합 A의 원소이다.

  • 집합 Intersection의 모든 원소는 집합 B의 원소이다.

맞다. 이는 교집합 A ∩ B의 정의이다. 인터섹션 타입은 타입 시스템이 여러 타입 간의 교집합을 표현하는 수단이다. 짐작했겠지만, 인터섹션(intersection)은 수학에서 교집합이라는 의미를 갖는다.

차집합 = ???

합집합과 교집합을 다루었으니 자연스레 ‘그럼 차집합은 어떻게 나타내지?’하는 질문이 들 수 있다.

집합 A에 대한 집합 B의 차집합 B - A는 다음 조건을 만족하는 집합으로 정의한다.

  • 집합 A에는 속하고 집합 B에는 속하지 않는 모든 원소는 집합 B - A의 원소이다.

  • 집합 AB에 동시에 속하는 모든 원소는 집합 B - A의 원소가 아니다.

타입스크립트 2.8에 추가된 Exclude 제너릭을 이용해 차집합을 나타낼 수 있다.

  • B 타입에 할당 불가능한 A 타입의 값은 모두 Exclude<A, B>에 할당 가능하다.

  • A & B 타입의 값은 모두 Exclude<A, B>에 할당 불가능하다.

Exclude 제너릭에 대해선 7장에서 보다 자세히 다룰 것이다.