TypeScript: Type System Ep.2 Structural Type System vs Nominal Type System, 타입 호환성, 타입 별칭

Structural Type System vs Nominal Type System


  • Structural Type System

    구조가 같으면 같은 타입으로 취급하는 방식이다.
    대표적인 언어로 타입스크립트가 있다.

    interface IPerson {
      name: string;
      age: number;
      speak(): string;
    }
      
    type PersonType = {
      name: string;
      age: number;
      speak(): string;
    }
      
    let personInterface: Iperson = {} as any;
    let personType: PersonType = {} as any;
      
    personInterface = personType; // 문제 없음
    personType = personInterface; // 구조가 동일하기 때문
    

    Iperson과 PersonType은 똑같은 구조를 가지고 있기 때문에 한쪽 타입을 한쪽 타입으로 대입할 수 있다.

    이러한 원리를 이용해서 타입스크립트에서는 해당 타입이 무엇인지 명시적으로 설정할 필요 없이 있던 것을 사용해도 된다. 구조가 동일하면 그냥 가져다가 쓰면 되기 때문이다.

  • Nominal Type System

    구조가 같아도 이름이 다르면 다른 타입이다.
    대표적인 언어로 C, Java가 있다.

    type PersonID = string & { readonly brand: unique symbol };
      
    function PersonID(id: string): PersonID {
      return id as PersonID;
    }
      
    function getPersonById(id: PersonID) {}
      
    getPersonById(PersonID('id-aaaaaa');
    getPersonById('id-aaaaaa');
    // error: 아무 문자열이나 넣을 수 없고 PersonID 형식으로 치환된 타입만 넣을 수 있다.
    
  • Duck Typing

    런타임에 발생하는 타이핑방식이다.
    만약 어떤 새가 오리처럼 걷고, 헤엄치고, 소리를 낸다면 우리는 그 새를 오리라고 부를 것이다. 라는 철학을 가지고 만든 것이다.
    대표적으로 Python이 있다.

    class Duck:
      def sound(self):
        print u"꽥꽥"
      
    class Dog:
      def sound(self):
        print u"멍멍"
      
    def get_sound(animal):
      animal.sound()
      
    def main():
      bird = Duck()
      dog = Dog()
      get_sound(bird)
      get_sound(dog)
    

타입 호환성 (Type Compatibility)


  • 서브타입
    • 같거나 서브타입인 경우에는 할당이 가능하다. 이러한 것을 공변이라고 부른다.

      // primitive type
      let sub7: string = '';
      let sup7: string | number = sub7;
      

      sup7은 string과 number의 조합이기 때문에 sub7보다 더 넓은 개념이다. 따라서 sub7을 넣을 수 있다. 이것이 공변이다.

      // object - 각각의 프로퍼티가 대응하는 프로퍼티와 같거나 서브타입이어야 한다.
      let sub8: { a: string; b: number } = { a: '', b: 1 };
      let sup8: { a: string | number; b: number } = sub8;
      

      a끼리 비교하고 b끼리 비교했을 때, sup8의 a가 더 넓은 개념을 가진다. 따라서 sub8을 sup8에 넣을 수 있다.

      // array - object와 마찬가지
      let sub9: Array<{ a: string; b: number }> = [{ a: '', b: 1 }];
      let sup9: Array<{ a: string | number; b: number }> = sub9;
      
  • 슈퍼타입
    • 함수의 매개변수 타입만 같거나 슈퍼타입인 경우 할당이 가능하다. 이것을 반변이라고 부른다.
      class Person {} // 가장 적다. 최상위
      class Developer extends Person {
        coding() {}
      } // 두번째로 많다.
      class StartupDeveloper extends Developer {
        burning() {}
      } // 안에 들은 함수가 가장 많다. 최하위
          
      // 인자 f에 함수가 들어간다. Developer를 인자로 받고 Developer를 리턴하는 형태
      function tellme(f: (d: Developer) => Developer) {}
          
      // Developer => Developer에다가 Developer => Developer를 할당하는 경우
      tellme(function dToD(d: Developer): Developer {
        return new Developer();
      });
          
      // Developer => Developer에다가 Person => Developer를 할당하는 경우
      tellme(function pToD(d: Person): Developer { // Person이 슈퍼타입인 경우
        return new Developer();
      });
          
      // Developer => Developer에다가 StartupDeveloper => Developer를 할당하는 경우
      tellme(function sToD(d: StartupDeveloper): Developer { // StartupDeveloper 서브타입
        return new Developer();
      });
      

      tellme에 인수 Developer와 Person이 들어왔을 때는 문제가 없다. Developer와 Developer는 같고 Person은 Developer보다 상위이기 때문이다.
      인수가 StartupDeveloper는 코드 자체에는 문제가 없다. 그러나 해당 코드를 function tellme~~ 코드에 넣었을 때 f에 맞지 않다. StartupDeveloper는 Developer보다 하위이기 때문에 해당 코드의 f는 burning()을 모른다. 그렇기에 논리적으로 문제가 있다.
      하지만, 타입스크립트는 해당 부분에 대해서 사용자에게 선택을 준다.

    • strictFunctionTypes

      해당 옵션을 키면, 함수를 할당할 때 함수의 매개변수 타입이 같거나 슈퍼타입이 아닌 경우(서브타입인 경우)에 에러를 통해서 경고한다.
      strictFunctionTypes 옵션을 키지 않으면 해당 코드 정도는 에러가 발생하지 않는다. (융통성을 준달까)

타입 별칭(별명) (Type Alias)


어떤 타입에 이름을 붙여준 것이다.
Interface와 비슷하지만 다르다.
Primitive Type, Union Type, Tuple, Function 등 어떤 형태의 타입을 길게 작성하지 않고 따로 빼서 이름을 만들어서 사용하는 것이다.
만들어진 타입의 별명(refer)으로 사용하는 것이지 타입을 만드는 것은 아니다.

  • Aliasing Primitive

    Primitive Type을 다른 이름으로 불러 보는 것이다.
    별다른 의미는 없다..

    type MyStringType = string;
      
    const str = 'world';
      
    let mystr: MyStringType = 'hello';
    mystr = str;
    
  • Aliasing Union Type

    유니온 타입은 A도 가능하고 B도 가능한 타입이다.
    길게 쓰는걸 짧게 쓸 수 있고 반복되는 것을 줄여준다.

    // 사용 x
    let person: string | number = 0;
    person = 'Mark';
      
    // 사용 o
    type StringOrNumber = string | number;
      
    let another: StringOrNumber = 0;
    another = 'Anna';
    
  • Aliasing Tuple

    튜플 타입에 별칭을 줘서 여러군데서 사용할 수 있게 한다.
    반복과 타이핑을 줄여준다.

    let person: [string, number] = ['Mark', 35];
      
    // 사용 o
    type PersonTuple = [string, number];
      
    let another: PersoonTuple = ['Anna', 24];
    
  • Aliasing Fuction

    매개변수를 별명을 만들어서 사용해준다.

    type EatType = (food: string) => void;
    
  • Aliasing과 Interface 구분

    어떤 타입이 목적이나 존재가치나 명확하면 interface.
    그렇지 않고, 어떤 대상을 가리키거나 별명으로서만 존재하면 type aliasing.

Comments