[typescript]타입을 집합으로 생각하기

SINHOLEE·2022년 8월 7일
7

프로그래밍

목록 보기
2/2

TLDR

타입은 값들의 집합을 대표하는 일종의 그릇

이렇게 생각하시는 분들과 함께 읽어보는게 어떨까요?

  • 타입은 값들의 집합이라는 명제가 이해가 안된다면,
  • 타입의 union과 intersection의 의미가 반대라고 생각한다면,
  • (심화) never, literal type(unit type), union type의 정의를 정확하게 모른다면,

이 포스팅을 읽고 다시 한 번 생각해 보면 좋을 것 같습니다.
(더 좋은 방법은 이펙티브 타입스크립트 책 사서 읽기!)

타입은 값들의 집합

타입스크립트가 동작하는 기본적인 규칙은 집합에 있습니다.

// script1
type StringOrNull = string | null

type Person = {
    name: string;
};

type HavingBirth = {
  birth: Date;
};

type LifeSpan = Person & HavingBirth; // { name: string; birth: Date; }

위와같은 타입형식은 타입스크립트를 이용하는 개발자라면 자주 보았던 모습일 겁니다.
그런데 가만히 살펴보면 조금 이상해 보입니다.
&는 수학적으로 교집합을 의미하고, PersonHavingBirth의 교집합은 없는거 아닌가요? HavingBirthname이 없고,Personbirth가 없잖아요?
( 여기서 저와 비슷한 의문이 전혀 생기지 않는다면, 타입은 값들의 집합이라는 의미를 이해하신 분들이라 생각합니다. ㅎㅎㅎ)

// script2

interface Female {
    name: string;
    femaleProp:number;
}
interface Male {
    name: string;
    maleProp:string;
}

type Intersection = Female & Male // {name:string } ??? 틀린 생각
type Union = Female | Male // {name:string; femaleProp:number; maleProp:string;} ??? 틀린 생각

오히려 위와 같이 교집합이니까 name만 교집합이고, 나머지는 합집합으로 나타내야하는 것 아닐까요?
결론부터 말하자면 이러한 사고방식은 틀렸습니다. 적어도 타입스크립트에서는 말이죠.

// script3

interface Female {
    name: string;
    femaleProp:number;
}

interface Male {
    name: string;
    maleProp:string;
}

type FemaleAndMale = {
    name: string;
    femaleProp:number;
    maleProp:string;
}


type Intersection = Female & Male // { name: string; femaleProp:number; maleProp:string;}

type Union = Female | Male // {name:string; femaleProp:number;}|{name:string; maleProp:string;}

사실 타입스크립트가 추론하는 방식은 위와 같습니다. 단어 자체의 의미와는 반대로, 교집합의 범위가 넓어지고, 합집합의 의미는 좁아진 것 같다는 느낌이 듭니다.
이는 타입스크립트의 타입이 속성에 대한 집합 이 아니라, 값들의 집합 으로 생각하기 때문입니다. (속성에 대한 집합이 script2와 같이 생각하는 것)

집합이란?

애초에 집합의 정확한 의미가 뭘까요? 위키백과에서는 집합은 주어진 성질을 만족시키는 대상들의 모임이다. 라고 정의합니다.

즉,

  • 정의한 타입에(주어진 성질)
  • 맞는(만족시키는)
  • 값들의 모임(대상들의 모임)

을 표현한 것이라고 풀어 말할 수 있습니다.

흠 아직 감이 잘 잡히지 않습니다. 위에서 얘기했던 타입들을 분석하면서 뽀개봅시다.

Intersction 타입을 분석해 볼까요?

Intersction라는 타입은 Female의 성질(name, femaleProp)과(and) Male의 성질(name, maleProp)을 모두 만족하는 값들을 표현하는 방법(타입)이라고 해석할 수 있습니다. 그렇기 때문에 name, maleProp, femaleProp 모든 프로퍼티가 존재하는 값이 유효한 타입이 되는 겁니다.

Union 또한, Female의 성질을 만족하거(or) Male의 성질을 만족하는 값들의 모임을 대표한다고 해석할 수 있습니다. 그렇기 때문에 Female이든 Male이든 두 성질중에 하나라도 만족하는 값이 있다면 Union타입에 만족하는 겁니다.

여기서 하나더 알 수 있는 사실은, 우리가 정의한 타입에 값을 할당 할 수 있냐 없냐를 판단한다는 겁니다. 결국 타입체크로 A에 B를 할당할 수 있는지 없는지 판단할 수 있는겁니다. 집합의 규칙을 이용해서...

즉, 타입은 값들의 집합을 대표하는 일종의 그릇인거죠.

벤다이어그램으로 그린다면 다음과 같습니다.

사진1

감이 조금 잡히실까요?

그렇기 때문에 우리는 FemaleOrMale 타입으로 선언된 변수에, Female조건을 만족하거나, Male조건을 만족하거나, 심지어 Female과Male의 조건을 동시에 만족하는 FemaleAndMale(type FemaleAndMale = {name:string; femaleProp:number; maleProp:string;})타입도 할당할 수 있는 겁니다.

값들의 집합

= 해당 집합(타입)에 속할 수 있는(가능성 있는 모든) 값들의 집합

= 해당 타입에 할당할 수 있는 값들의 집합

= 'A는 B를 상속','A는 B에 할당 가능', 'A는 B의 서브타입' 이라는 말은 모두 A는 B의 부분집합이라는 의미(effective typescript 46p)

심화1: 집합공간과 never, literal type, union type의 관계

  • 집합에는 원소라는 개념이 있는데, 타입스크립트의 표현과 집합의 표현을 매칭하여 생각할 수 있습니다.
  • never는 집합의 개념으로 공집합이라는 의미
    • 즉, 값이 존재할 수 없다는 의미
    • type NeverExample = string & number
      • 문자열이면서 숫자타입인 값은 존재할 수 없으므로 never타입이다.
  • 리터럴타입(literal type) 혹은 유닛타입(unit type)은 값이 하나인 원소라는 뜻
    • type Foo = 'foo'; 의 경우 'foo' 라는 리터럴 타입의 집합으로, 할당 가능한 값은 오직 'foo'밖에 없다.
  • 유니온 타입은 두개 이상의 원소가 존재한다는 뜻
    • type StringOrNumber = string | number 타입은 string 혹은 number 아무 타입의 값이 할당될 수 있다는 뜻
    • 심지어 const foo:Foo='foo'라는 변수 fooStringOrNumber인 타입의 변수에 할당할 수 있다.
      • 'foo' 라는 리터럴 타입은 string타입의 서브타입이기 때문에

더 자세히 알고 싶으시다면 이펙티브 타입스크립트 46p를 보세요!

심화2: 타입의 개수가 3개 이상 조합하고 싶을때?

type A = {
  one: string;
  two: number;
};
type B = {
  two: string;
};
type C = {
  two: string[];
  three: string;
};

type AandB = A & B; // {one:string; two:number & string; } two is never
type AandC = A & C; // {one:string; two:number & string[]; three:string;} two is never
type BandC = B & C; // { two:string & string[]; three:string;} two is never
type AandBandC = A & B & C; // {one:string; two:number & string & string[]; three:string;} two is never

type AorB = A | B; 
type AorC = A | C; 
type BorC = B | C; 
type AorBorC = A | B | C;


// 아래 변수 1~11중에 타입에러를 내뿜는 변수는 무엇일까요?
const A_OR_B1: AorB = {} as A;
const A_OR_B2: AorB = {} as B;
const A_OR_B3: AorB = {} as C;
const A_OR_B4: AorB = {} as AandB;
const A_OR_B5: AorB = {} as AandC;
const A_OR_B6: AorB = {} as BandC;
const A_OR_B7: AorB = {} as AandBandC;
const A_OR_B8: AorB = {} as AorB;
const A_OR_B9: AorB = {} as AorC;
const A_OR_B10: AorB = {} as BorC;
const A_OR_B11: AorB = {} as AorBorC;

위의 질문을 타입체커의 힘을 빌리지 않고 찾아낸다면 타입은 값들의 집합이다라는 의미를 명확하게 이해한 것입니다.

심화2:힌트

profile
엔지니어로 거듭나기

0개의 댓글