개발/이펙티브 타입스크립트

[이펙티브 타입스크립트+7] 타입이 값들의 집합이라고 생각

Junghyun Kim 2021. 8. 3. 23:44
반응형

TypeScript: Documentation - TypeScript for Java/C# Programmers (typescriptlang.org)

2장 타입스크립트의 타입 시스템
아이템 7 | 타입이 값들의 집합이라고 생각하기

3줄 요약
1. 값들의 집합을 타입이라고 한다.
2. unknown과 any는 본질적으로 다른 타입이다.
3. 부분 집합이다 / 할당 가능하다 / 상속받았다는 동일한 의미를 가지고 있다.

참고) 이펙티브 타입스크립트는 타입스크립트의 기본 그 이상을 다룹니다.


자바스크립트(ES6)에서 타입이란 데이터 타입을 이야기하며 총 7개입니다. 원시 타입인 number, string, boolean, undefiend, null, symbol와 객체 타입으로 구성되어 있습니다.
타입스크립트에서는 타입을 값들의 집합이라고 생각해야 합니다. 그리고 그 타입은 프로그래머가 정의할 수 있으며 그 수는 무한히 많습니다. 그리고 이 타입은 타입 공간에 존재합니다. 값의 공간과는 분리되어 있습니다. (자세한 내용은 다음 아이템에서 다루겠습니다.)

먼저 가장 작은 집합부터 이야기하겠습니다. never라는 타입입니다. 이 타입으로 선언이 되면 값을 할당할 수 없습니다. 값들이 없는 집합.
즉, 공집합입니다.

never 타입, 즉 공집합에는 값을 할당할 수 없다.

반대로 가장 큰 집합은 unknown입니다. 어떠한 값이든 할당할 수 있는 집합입니다. 전체 집합이라고 생각하면 됩니다.

unknow 타입, 즉 전체집합에는 어떠한 값이 들어올 수 있다.

unknown 타입을 보니 어디선가 any 타입의 기운이 느껴집니다.
그러나 unknown은 타입 체커에게 "나는 unknown 타입의 값들을 할당받을 수 있어"라고 말한다면
any는 타입 체커에게 "내가 무슨 짓을 하든 넌 상관하지 마"라고 말하는 것과 같습니다. 타입 체커를 무시하지요.

number 타입에 unknown 타입을 넣으면 에러를 내지만, any 타입은 에러를 내지 않습니다.

함수 doSomethingWithNumber의 파라미터 value는 number타입입니다.
number 타입은 전체 집합인 unknown 타입의 부분 집합이라고 말할 수 있습니다.
unknownValue에는 number 타입뿐만 아니라 string 타입의 값도 할당될 수 있기에 타입 체커는 옳지 않다며 에러를 냅니다.
그러나 any는 타입 체크를 무시하기에 에러를 내지않습니다.
이것이 unknown과 any 타입의 차이점입니다.

다음으로는 유닛(unit) 타입이라고도 불리는 리터럴(literal) 타입입니다.
원소가 하나로만 이루어진 집합이라고 할 수 있습니다.

원소가 하나 밖에 없는 집합.

집합을 더하면 합집합이 됩니다. 이를 유니온(union) 타입이라 합니다. 기호 '|'를 이용하여 만들 수 있습니다.

합집합을 나타내는 유니온 타입.

합집합이 나타내는 것이 있으니 교집합 또한 나타낼 수 있습니다. 기호 '&'을 사용하여 인터섹션을 나타낼 수 있습니다.

ABType과 A 타입의 공통 원소는 "A"이다.

흥미로운 것은 이 인터섹션이 오브젝트 타입에 적용될 때입니다.
아래 사진과 같이 Creature 타입과 Person 타입이 정의되어 있을 때 이들의 인터섹션은 어떻게 될까요?
언뜻 봐서 프로퍼티의 교집합으로 생각하면 Creature 타입이 인터섹션으로 보입니다.

Creature 타입과 Person 타입.

하지만 정답은 Person 타입입니다.
오브젝트는 지난 아이템 4에서 언급한 바와 같이 프로퍼티를 제약 조건이라고 생각하시면 됩니다. 
값의 공간에 세상 무수히 많은 오브젝트들이 갖고 있다고 가정해보겠습니다. 그 오브젝트들의 생김새는 무척이나 다양합니다.
무수히 많은 오브젝트들 중에서 Person 타입의 오브젝트가 될 수 있는 오브젝트는 동시에 Creature이라는 오브젝트가 됩니다.
그러나 반면에 Creature 타입의 오브젝트들은 항상 Person의 오브젝트가 된다고 말할 수 없습니다. nationality라는 제약조건이 더 있기 때문입니다.
따라서, Creature는 상위 집합이고 Person은 부분 집합이 됩니다.
이러한 관계에 따라 Creature와 Person의 교집합은 Person이 됩니다.

Person 타입은 Craeture 타입의 부분 집합이다.

그리고 Creature 타입과 Person 타입의 관계는 상속으로도 설명이 가능합니다.

Creature 타입을 상속받는 Person 타입.

다시 말해, 부분 집합이라는 것은 특정 타입을 상속받는 것과 동일한 의미를 가집니다.

아까 doSomethingWithNumber라는 함수에 unknown 타입의 변수를 파라미터로 넘긴 과정을 다시 살펴보겠습니다.
에러 문구에 대해서 확인해보겠습니다.

에러: unknown 타입의 인자는 number 타입의 파라미터에 할당할 수 없다.

문구를 살펴보면 unknown 타입의 인자를 number 타입의 파라미터에 할당할 수 없다라고 합니다.
상위 집합을 부분 집합에 할당 불가능하다는 말과 동일합니다.
그러므로 아래 표와 같이 정리가 가능합니다.

A 타입은 B 타입의 부분 집합이다. B는 A의 상위 집합이다.
A 타입은 B 타입을 상속받았다. B 타입은 A 타입을 상속받지 않았다.
A 타입은 B 타입에 할당할 수 있다. B 타입은 A 타입에 할당할 수 없다.

 

* 상속받는다는 용어는 타입의 관점에서와 클래스의 관점에서는 미묘한 차이가 있습니다. 여기서는 타입의 관점에서 이야기힙니다.

 

Materials From
1. <이펙티브 타입스크립트>(댄 밴더캄 지음, 장원호 옮김, 인사이트 2021)
2. 타입스크립트 공식 홈페이지 | https://www.typescriptlang.org/

 

Typed JavaScript at Any Scale.

TypeScript extends JavaScript by adding types to the language. TypeScript speeds up your development experience by catching errors and providing fixes before you even run your code.

www.typescriptlang.org

 


참고)
배열과 튜플의 관계는 어떻게 될까요?
number[] 타입과 [number, number] 타입은 엄연히 다른 타입입니다.

먼저 튜플 타입을 배열 타입에 할당해보겠습니다.

튜플은 리스트에 할당 가능하다. 따라서, 튜플은 리스트의 부분 집합이다.

정상적으로 할당이 가능합니다. 그렇다면 반대는 어떨까요?

리스트는 튜플에 할당 불가능합니다.

리스트는 튜플의 상위 집합이니 당연히 할당되지 않습니다.

이런 관점에서 바라볼 때, 트리플을 튜플에 할당 가능하지 않을까요?
세 번째 원소를 무시하고 첫 번째와 두 번째 원소를 튜플에다가 할당하면 가능할 것 같습니다.

에러: Source has 3 element(s) but target allows only 2.

불가능합니다. 이유가 흥미롭습니다. 2개의 요소의 개수만 가능하다고 합니다.
어떻게 이런 일이 일어난 걸까요?

triple의 오브젝트 모델링. length의 값이 3으로 고정되어있다.

triple을 보면 length가 3의 값으로 고정되어 있습니다. 그리고 이는 변하지 않습니다.
마찬가지로 tuple은 length가 2의 값으로 고정되어 있습니다.
length가 2의 값으로 고정되어 있으니 해당 변수에 할당을 하려면 마찬가지로 length가 2인 타입들만 할당할 수 있는 겁니다.

tuple의 오브젝트 모델링. length의 값이 2로 고정되어있다.


참고2)
Exclude라고 하는 유틸리티 타입이 있습니다. 타입에서 특정 타입을 제거할 수 있습니다. 

https://www.typescriptlang.org/docs/handbook/utility-types.html#excludetype-excludedunion
ABCWithoutC 타입에 "C"를 할당할 수 없다.

이렇게 사용할 수 있습니다.
그러나, 이런 유틸리타 타입을 이용해서 새로운 타입을 만들어낼 때도 그 타입이 유효한 타입이 되어야합니다.
가령 number 타입이나 string 타입에서 특정 유니온 타입을 제거하는 것은 타입스크립트에서 유효한 타입이라고 여기지 않습니다.

에러없이 잘 할당된다.

NumberWithoutZero는 여전히 number타입이고, StringWithoutZero 타입은 여전히 string 타입입니다.

반응형