[2장] 타입 - 북 스터디

코딩 마을 방범대 북 스터디 DAY 1

·

8 min read

[2장] 타입 - 북 스터디
🤓 스터디원 🤓
강지윤 @eeeyooon
이예솔 @lulla-by
이성령 @sryung1225
이에스더 @Stilllee
채하은 @chaehaeun



코딩 마을 방범대 : 2장. 타입

질문과 답변

1. 덕 타이핑과 구조적 타이핑

자바스크립트의 덕 타이핑/타입스크립트의 구조적 타이핑의 공통점과 차이점

타입스크립트의 구조적 타이핑은 자바스크립트의 덕 타이핑을 기반으로 하는데요. 공통점, 차이점으로 무엇이 있을까요?

또, C++과 자바와 같은 언어에서 사용되는 명목적 타이핑과의 차이는 무엇이 있을까요?

Stilllee
덕 타이핑은 런타임에 타입을 검사하며 주로 동적 타이핑에서 사용됩니다.
반면에 구조적 타이핑은 컴파일 타임에 타입체커카 타입을 검사하며 정적 타이핑에서 사용됩니다.

이 둘은 타입의 이름으로 타입을 구별하는 명목적타이핑과는 달리 타입의 구조로 타입을 구별한다는 공통점이 있습니다.
lulla-by
공통점으로는 타입스크립트와 자바스크립트 모두 명목적타이핑과는 달리 객체가 가진 구조를 바탕으로 타입을 구분한다는 것입니다.
차이점으로는 타입스크립트의 구조적타이핑은 컴파일 시점에 타입이 체크되는 반면 자바스크립트의 덕타이핑은 런타임 시점에 타입이 체크된다는 점입니다.
eeeyooon
덕 타이핑과 구조적 타이핑 모두 객체 변수, 메서드 같은 필드를 기반으로 타입을 검사한다는 점에서 동일하지만, 타입을 검사하는 시점이 다르다는 차이점이 있습니다.
덕 타이핑은 런타임에 타입을 검사하고, 구조적 타이핑은 컴파일 타임에 타입을 검사합니다.
sryung1225
  • 명목적(nomianl): 이름으로 타입을 구분 (ex. C++, JAVA)
  • 구조적(structural): 구조로 타입을 구분 (ex. JS, TS)
  • 덕(duck): 어떤 함수의 매개변수 값이 올바르게 주어지면 어떻게 만들어졌는지에는 상관 없이 사용 (ex. JS)

  • 덕 타이핑과 구조적 타이핑은 서로 구분되지만 구조로 타입을 구분한다는 점에서 공통점을 가집니다. 하지만 타입을 검사하는 시점에서 차이를 가집니다. 구조적 타이핑은 컴파일 타임에 타입 검사를, 덕 타이핑은 런타임에 타입 검사를 합니다.

    명목적 타이핑은 구조랑 상관없이 이름을 기준으로 타입을 구분한다는 점이 구조적 타이핑과의 차이점입니다.

    출제자 : chaehaeun

    공통점

    interface Pet {
      name: string
    }
    
    interface Cat {
      name: string
      age: number
    }
    
    let pet: Pet;
    let cat: Cat = { name: "Zag", age: 2 };
    
    // ✅ OK
    pet = cat;
    
    • 객체나 함수가 가진 구조적 특징을 기반으로 타이핑하는 방식입니다.
    • 즉, 이름이 서로 다르더라도 객체가 가지고 있는 프로퍼티가 동일하다면 서로 호환이 가능합니다.

    차이점

    • 타입을 검사하는 시점의 차이가 있습니다. 자바스크립트는 런타임에 타입 검사를, 타입스크립트는 컴파일타임에 타입 검사를 합니다.

    명목적 타이핑과의 차이?

    • 명목적 타이핑의 경우 같은 구조적 특징을 가지고 있더라도 '이름'이 다르다면 서로 다른 타입으로 취급됩니다.




    2. 타입스크립트와 구조분해할당

    타입스크립트의 값과 타입에 관련하여 구조분해할당시의 주의점은?

    타입스크립트에서 값과 타입은 별도의 네임스페이스에 존재합니다. es6에 도입된 구조분해할당을 사용할 경우 다음과 같은 코드는 에러를 발생시키는데 그 이유는 무엇 때문인가요? 또한 타입스크립트에서 함수 작성 시 구조분해할당을 올바르게 적용하기 위해서는 다음의 코드를 어떻게 수정해야 할까요?

    interface Person{
      name: string;
      age: number;
    }
    
    function testFunc ( { person:Person, description:string, level:number} ){
      console.log(person, description, level)
    }
    
    Stilllee
    타입스크립트에서 구조 분해 할당을 사용할 때에는 타입을 명시적으로 지정해줘야 하며, 문제의 코드는 Personstring을 타입이 아닌 값으로 해석하기 때문에 오류가 발생합니다.

    올바른 작성법은 아래와 같습니다.
    
        function testFunc({
          person,
          description,
          level,
        }: {
          person: Person;
          description: string;
          level: number;
        }) {
          console.log(person, description, level);
    }
    sryung1225
    타입을 값으로 혼동해서 문제가 발생합니다.
    정확히는 person: Person 을 객체의 key: value 로 착각한 결과입니다.
    때문에 이 경우에는 값과 타입을 구분해서 작성해줘야 합니다.
    
      // 🧿 answer
      function testFunc({
          person,
          description,
          level,
      }: {
          person: Person,
          description: string,
          level: number,
      }) {
          console.log(person, description, level);
      };
    
    chaehaeun
    타입스크립트가 타입과 값을 혼동하게 되기 때문입니다.
    예제의 경우 person이라는 keyPerson이라는 값을 할당한 것인지, 아니면 타입을 지정한 것인지 타입스크립트가 판단하기 어렵기 때문에 에러가 난 것입니다. 구조 분해 할당을 사용할 때는 값과 타입을 구분해서 작성해줘야 됩니다.
    
    interface Person {
      name: string;
      age: number;
    }
    
    function testFunc({ person, description, level }: { person: Person; description: string; level: number }) {
      console.log(person, description, level);
    }
    

    출제자 : lulla-by

    작성자가 값의 공간과 타입의 공간을 헷갈려서 작성한 코드입니다.
    이러한 경우에는 TS에서는 구조분해할당을 진행한 부분이 값의 공간으로 해석되어 개발자가 의도한 Type Annotation이 아닌 key-value 형식으로 해석됩니다.
    따라서 해당 코드에서 올바르게 구조분해할당을 사용하기 위해서는 다음과 같이 값과 타입을 구분하여 작성해야합니다.

    function testFunc ( { person, description, level}: { person:Person; description:string; level:number} ){
    console.log(person, description, level)
    }
    




    3. 함수 자체의 타입지정

    타입 스크립트에서 함수 자체의 타입을 지정하는 방법과 그 예시를 설명해주세요.

    function add(a: number, b: number): number {
      return a + b;
    }
    

    위의 예시는 타입 스크립트에서 함수를 작성할 때 매개변수와 반환 값에 대한 타입을 지정하는 문법을 설명한 것입니다. 그럼 함수 자체의 타입은 어떻게 지정할 수 있나요? 그 방법과 예시를 설명해주세요.

    lulla-by
    함수자체의 타입을 지정하는 방식은 호출 시그니처를 사용하면 됩니다!
    
    type add = (a: number, b: number) => number;
    
    Stilllee
    타입스크립트에서 함수 자체의 타입을 명시할 때에는 화살표 함수 방식으로 호출 시그니처를 정의합니다.
    
    type add = (a: number, b: number) => number;
    
    sryung1225
    호출 시그니처(Call Signature)를 사용합니다.
    
    function add(a: number, b: number): number {
      return a + b;
    }
    
    // 🧿 answer
    type add = (a: number, b: number) => number;
    
    참고로 호출 시그니처는 함수에 마우스를 올려서 볼 수 있습니다 👀
    chaehaeun
    호출 시그니처를 사용하면 됩니다. 화살표 함수와 작성법이 유사합니다.
    
    type add = ( a : number, b : number ) => number
    

    출제자 : eeeyooon

    함수 자체의 타입을 지정하려면 호출 시그니처를 정의하는 방식을 사용하면 됩니다. 함수 타입은 해당 함수가 받는 매개변수와 반환하는 값의 타입으로 결정되는데, 호출 시그니처는 이러한 함수의 매개변수와 반환 값의 타입을 명시하는 역할을 합니다.

    type Add = ( a: number, b: number) => number; // call signature
    
    const add: Add = (a, b) => a + b
    

    예시 코드를 보면 먼저 함수의 매개변수와 반환타입을 type으로 미리 선언해 두고, Add 함수의 타입으로 지정하였습니다. 해당 함수에 커서를 올리면, 매개변수와 리턴 값의 타입 정보를 미리 볼 수 있습니다.

    image

    이렇게 호출 시그니처를 정의해서 사용하면, 개발자는 미리 해당 함수의 type을 인지하여 코드를 작성할 수 있고 타입 지정과 함수 구현을 분리해서 작성할 수 있다는 장점이 있습니다. 또한 호출 시그니처는 여러 함수에 사용이 가능합니다.

    일반적으로 자바스크립트에서는 함수를 작성할 때 function 키워드를 사용해서 작성하거나 화살표 함수 방식으로 작성합니다. 반면 타입스크립트에서는 함수 자체의 타입을 명시할 때 화살표 함수 방식으로만 호출 시그니처를 정의합니다.




    1. 타입과 변수를 같은 이름으로 정의

    타입과 변수를 같은 이름으로 정의하여 사용할 수 있는 이유는?

    타입스크립트에서는 아래 코드와 같이 타입과 변수를 같은 이름으로 정의하여 사용할 수 있습니다. 이것이 가능한 이유를 컴파일타임 과 연관지어 설명하세요.

    interface Hello {
      name: string;
    }
    
    const Hello: Hello = { name: "hi" };
    
    lulla-by
    타입스크립트에서 타입은 컴파일 시점에 결정되고 런타임 시점에 제거됩니다. 그렇기 때문에 타입과 변수의 이름이 같아도 사용이 가능해요!
    Stilllee
    타입스크립트에서 타입과 값은 서로 다른 네임스페이스에 존재합니다.
    컴파일 과정을 거쳐 타입 정보는 제거되고, 자바스크립트 코드만 남게되는데 이때 런타임에서는 변수만 존재하게 됩니다.

    이러한 구조 덕분에 타입과 변수는 충돌하지 않고 같은 이름으로 사용할 수 있습니다.
    chaehaeun
    타입스크립트에서는 값과 타입이 별도의 네임스페이스에 존재합니다. 컴파일타임에 타입 정보를 사용해 타입 검사를 하고, 컴파일이 완료되고 코드가 자바스크립트로 변환되면 타입 정보는 모두 제거됩니다.
    ⇒ 같은 이름을 가진 타입/값은 컴파일타임/런타임 서로 다른 문맥에서 사용되기 때문에 충돌하지 않습니다.

    출제자 : sryung1225

    타입스크립트는 컴파일 타임에 타입 선언을, 런타임에 변수 선언을 하기 때문에 서로 충돌하지 않습니다

    타입스크립트는 자바스크립트에 새로운 기능을 추가해서 향상 시킨 슈퍼셋(Superset) 언어입니다. 이런 타입스크립트는 정적 타입 언어로 런타임보다 우선인 컴파일 타임에서 타입을 결정합니다. 컴파일 결과물로 타입이 모두 제거된 자바스크립트 소스코드가 반환되는 부분이 그 증거입니다.

    즉, 타입 선언(interface Hello)과 변수 선언(const Hello) 둘은 서로 충돌하지 않습니다. 타입 선언된 부분은 변수 선언 이전에 제거됩니다. 때문에 타입과 변수를 같은 이름으로 선언해도 서로 충돌하지 않게 됩니다.

    다만 가독성을 해치고 오해의 소지가 높기 때문에 일반적으로 사용하지 않습니다.

    (연관 페이지: 36-37p (2.1.3 정적 타입과 동적 타입), 41p (2.1.5 컴파일 방식))


    추가 논의

    호출 시그니처와 함수 타입 표현식

    호출 시그니처와 함수 타입 표현식의 차이는 무엇인가?

    타입스크립트 공식 문서에서 함수 타입 표현식과 호출 시그니처에 대한 내용을 확인했을 때에는

    type GreetFunction = (a: string) => void;
    function greeter(fn: GreetFunction) {
      // ...
    }
    

    러한 방식이 함수 타입 표현식의 예시 코드라고 나와있다.

    그러나, 책에 나온

      type Add = (a: number, b: number) => number;
    

    이 예시코드와의 차이점을 잘 모르겠다는 의견이 나왔다.

    공식 문서에서는

    If we want to describe something callable with properties, we can write a call signature in an object type: Note that the syntax is slightly different compared to a function type expression - use : between the parameter list and the return type rather than =>.

    만약 우리가 호출 가능하면서 프로퍼티를 가진 무언가를 설명하려고 하면, 객체 타입에 호출 시그니처 를 사용하여 표현할 수 있습니다. 이 문법은 함수 타입 표현식과 다릅니다. 매개변수 타입과 반환값의 타입 사이에 =>가 아닌 :를 사용해야 합니다

    라고 호출 시그니처를 설명하는데, 이 내용이 책에서 말하는 호출 시그니처와 다른 것 같고, 오히려 책에서 나온 호출 시그니처의 예제나 설명은 함수 타입 표현식과 더 가까워 보이는데 둘의 차이가 무엇인지 명확히 알고싶다는 것이다.

    나또한 의문이 가시지 않아 관련 내용을 찾아보았다.

    함수 타입 표현식 (Function Type Expressions)

    • 함수 타입 표현식은 함수의 매개변수와 반환 타입만을 정의하는 데 사용된다. 이는 순수한 함수의 시그니처를 나타내며, 추가적인 프로퍼티를 정의하는 것은 포함하지 않는다.
      type GreetFunction = (a: string) => void;
      

    호출 시그니처 (Call Signatures)

    • 호출 시그니처는 객체 타입 내에서 함수 타입을 정의할 때 사용된다. 이는 객체가 함수처럼 호출될 수 있음을 나타내며, 동시에 추가적인 프로퍼티를 정의할 수 있다.
    • 호출 시그니처는 :를 사용하여 매개변수 타입과 반환 타입을 구분한다.
    • 호출 시그니처를 사용하면 함수 타입과 일반 객체 프로퍼티를 결합한 하이브리드 타입을 정의할 수 있다. 이는 함수 기능과 함께 추가적인 프로퍼티나 메서드를 가진 객체를 타입으로 정의할 수 있게 해준다.
      interface SomeType {
      (a: number, b: number): number;
      someProperty: string;
      }
      

    본문에서 언급하고 있는 내용에는 오해의 소지가 있다.

    type Add = (a: number, b: number) => number;
    
    • 이 코드는 함수 타입 표현식의 예시로, 호출 시그니처의 예시가 아니다.
    • 호출 시그니처는 화살표 함수 구문으로 작성되지 않는다.
    • 이 문법은 화살표 함수의 구문을 빌려와서 TypeScript에서 함수의 타입을 정의하는 방법을 나타낸다.
    • 여기서 Add는 두 개의 number 타입 매개변수를 받고, number 타입의 값을 반환하는 함수의 타입을 정의한다.


    위와 같은 내용으로 짐작하건데, 호출 시그니처와 함수 타입 표현식을 혼동하고 있는 것으로 보인다는 것이 내 결론이다.