JavaScript/TypeScript

Type의 근원을 알아보고 타입스크립트의 Type System 바라보기

남희정 2023. 9. 29. 17:29

타입스크립트에서의 타입은 자바스크립트의 타입과 무엇이 다를까? 이전에 배운 객체지향에서의 타입, 타입시스템이 반영되어 있는 걸까? 타입은 프로그래밍에서 어떤 의미인가 🤔

코드보다 이론 위주의 포스팅이 될 것이라 유의바람.


타입 Type

타입을 알기 위해선 추상화 Abstraction에 대한 이해가 선행되어야 한다.

추상화 抽象化
어떤 양상, 세부 사항, 구조를 명확하게 이해하기 위해 특정 절차나 물체를 의도적으로 생략하거나 감춤으로써 복잡도를 극복하는 방법.

 

복잡성을 다루기 위해 추상화는 두 차원에서 이루어진다.

1. 구체적인 사물들 간의 공통점만 취하고 차이점을 버리는 일반화를 통해 단순하게 만드는 것.

2. 중요한 부분을 강조하기 위해 불필요한 세부 사항을 제거함으로써 단순하게 만드는 것.

➡️ 이 두 가지는 동시에 쓰일 수도 있다.

 

추상화는 우리의 일상에 여기저기 녹아있다. 우리는 저마다의 개성을 갖고 있지만 "사람"이라는 개념 Concept으로 분류 Classification된다. 이런 추상화는 우리가 살아가면서 겪거나 학습을 통해 사물과 그 과정의 본질적 특징을 자연스레 분류하고 개념으로 정립시킨다. 개념이 우선이 되는 게 아니라는 것이 중요하다! 

 

즉, 개념은 사물ㆍ과정이 여러 가지로 비교되어 사고 속에서 구성요소로 분류되고 본질적 특성이 비본질적 특성으로부터 구별(추상화)되어 집합을 이룬다.

 

이러한 추상화 과정을 거쳐야하는 개념Concept을 컴퓨터 공학자들은 수학으로부터 차용해왔다. 그것이 타입Type이다. 

수학의 집합과 프로그래밍의 타입이 공유하는 성질이 무엇인지 그려질 것이다. 프로그래밍에서의 Value 와 Type 그리고 집합에서의 원소와 집합은 굉장히 유사하다. 타입(집합)이란 결국 값(원소)의 집합..!

 

타입Type은 개념과 동일하다. 따라서 타입이란 우리가 인식하고 있는 다양한 사물이나 객체에 적용할 수 있는 아이디어나 관념을 의미한다. 어떤 객체에 타입을 적용할 수 있을 때 그 객체를 타입의 인스턴스라고 한다. 타입의 인스턴스는 타입을 구성하는 외연인 객체 집합의 일원이 된다.

 

타입을 분류 장치로 사용하기 위해선 세 가지 관점에서의 정의가 필요하다.

  • 심볼 Symbol : Type을 가리키는 간략한 이름이나 명칭
  • 내연 Intension : Type의 완전한 정의 (내연의 의미를 이용해 객체가 타입에 속하는지 여부를 확인할 수 있다.)
  • 외연 Extension : Type에 속하는 모든 객체들의 집합

 

데이터 타입 Data Type 과 타입 시스템 Type System

컴퓨터가 어떤 작업을 수행하기 위해선 작업에 필요한 데이터를 메모리 안으로 불러들여야 한다. 메모리의 세상엔 타입이라는 질서가 존재하지 않는다.  == Untyped

타입이 없는 체계에서 모든 데이터는 일련의 비트열 bit string 으로 구성된다. 비트열을 바라보며 어떤 의미를 가진 데이터인지 설명할 수 있는 사람은 없을 것이다.

메모리의 세상은 Untyped이다.

 

타입이 없는 무질서를 타파하기 위해 메모리 안의 데이터에 의미를 부여하기 시작했다. 

 

데이터 타입 Data Type은 메모리 안에 저장된 데이터의 종류를 분류하는 데 사용하는 메모리 집합에 관한 메타데이터다. 데이터에 대한 분류는 암시적으로 어떤 종류의 연산이 해당 데이터에 대해 수행될 수 있는지를 결정한다.

 

데이터에 어떤 연산자를 적용할 수 있느냐에 따라 데이터 타입이 결정된다


컴퓨터 안의 데이터를 목적에 따라 분류하기 시작하여 타입 시스템 Type System이 생겼다. 타입 시스템의 목적은 데이터가 잘못 사용되지 않도록 제약 사항을 부과하는 것이다.

🌟  중요한 사실

  1. 프로그래밍에서의 타입Type은 어떻게 사용되느냐에 관한 것이다. 
    데이터가 어떤 타입에 속하는지를 결정하는 것은 데이터에 적용 가능한 작업이다. 일반적으로 이 작업을 연산자 Operator라고 한다.
    어떤 데이터에 어떤 연산자를 적용할 수 있느냐가 그 데이터의 타입을 결정한다.
  2. 타입에 속한 데이터를 메모리에 어떻게 표현하는지는 외부로부터 철저히 감춰진다.
    데이터 타입의 표현은 연산 작업을 수행하기에 가장 효과적인 형태가 선택되며 개발자는 표현 방식을 몰라도 데이터를 사용하는 데에는 지장이 없다.

 

TypeScript에서의 타입Type

나는 TypeScript의 타입Type을 JavaScript에서 다루는 값의 형태를 더욱 Typical 하게 제한을 두는 행위라고 정의했다.

짧게는, JavaScript를 추상화 시키는 것으로 정의내려보았다! 이전에 설명한 것들과 TypeScript의 타입을 알아보면서 쉽게 비유를 계속 시도해보겠다. 

기본 타입

타입스크립트의 가장 기본적인 타입은 자바스크립트의 7가지 기본 원시 타입primitive type과 동일하다.

  • null
  • undefined
  • boolean
  • string
  • number
  • Bigint
  • Symbol 

특별한 타입

자바스크립트와 대응되지 않는 타입도 있다! 헷갈릴 수 있으니 위에서 설명한 집합과 연관지어서 설명하겠다.

Top 타입

시스템에서 가능한 모든 값을 나타내는 타입. 모든 다른 타입의 값은 타입이 top인 위치에 제공될 수 있다. 
즉, 모든 타입은 top 타입에 할당될 수 있다.

 

1. any 타입

any 타입은 모든 타입의 위치에 제공될 수 있다는 점에서 top 타입처럼 작동할 수 있다. any는 일반적으로 console.log의 매개변수와 같이 모든 타입의 데이터를 받아들이는 위치에서 사용한다.

let variable: any;

variable = 10;     // number
variable = 'hello'; // string
variable = true;   // boolean

console.log(variable);

 

다만 any는 타입스크립트가 해당 값에 대한 할당 가능성 또는 멤버에 대해 타입 검사를 수행하지 않도록 명시적으로 지시한다는 문제점을 갖는다. 타입 검사기를 건너뛰려 할 때 유용하지만 타입 검사를 비활성화한다는 것은 타입스크립트의 목적성에 부합하지 않는 행위..!

 

let value1: any;
let value2: unknown;

let numberValue: number = 5;

value1 = numberValue;  // "any" 타입이므로 할당 가능
value2 = numberValue;  // "unknown" 타입이므로 할당 불가능

// 명시적인 타입 검사 후에 할당 가능
if (typeof value2 === 'number') {
    let numberValue2: number = value2;
}

 

어떤 값이든 될 수 있음을 나타내려면 unknown 타입이 훨씬 안전하다.

 

2. unknown

타입스크립트에서 unknown 타입은 진정한 top 타입이다. 모든 객체를 unknown 타입의 위치로 전달할 수 있다는 점에서 any 타입과 유사하다. unknown 타입과 any 타입의 주요 차이는 훨씬 더 제한적으로 취급한다는 점이다. 
✔️ 타입스크립트는 unknown 타입 값의 속성에 직접 접근할 수 없다.

✔️ unknown 타입은 top타입이 아닌 타입에는 할당할 수 없다.

➡️ 이런 제한으로 인해 가능하다면 any 대신 unknown을 사용하길 추천한다.

 

instanceof 나 typeof 또는 타입 assertion을 사용하는 것처럼 타입이 제한된 경우에만 타입스크립트가 unknown타입에 접근할 수 있다. 

 

타입 좁히기 type narrowing

타입스크립트를 공부한다면 타입을 제한하다, 좁히다라는 말이 자주 사용되는 걸 알 수 있다. 이런 개념이 처음에는 모호할 수 있다. 제한된다는 것이 어떤 제약 사항을 두고 Pass 시키는 것인가.. 타입을 좁히다는 것도 관문을 좁힌다는 것인지..? 이런 흐름도 맞는 말이지만 위에서 설명한 집합의 개념으로 좀 더 쉽게 이해할 수 있다.

 

타입의 외연을 좁히다, 특정 타입에 속하는 값들의 범위를 축소하거나 제한하여 특정한 조건(내연)을 갖춘 집합을 또 만들어낸다는 것이다. 

⚠️ 여기에서 타입의 계층으로 특수화와 일반화, 슈퍼타입과 서브타입 등의 개념이 나올 수 있는데 과해질 수 있으므로 넘어가겠다. 더욱 알고 싶다면 <객체지향의 사실과 오해>를 읽어보기를 추천한다.

특수화와 일반화

any와 unknown의 차이점을 외연과 내연으로 빗대어 설명하면 어떻게 표현할 수 있을까? 
any는 unknown의 외연에 존재한다. 그것을 구분Classification하는 내연(조건)으로는 "명시적인 타입 확인"이다.

이들이 타입들의 제일 Top 타입, 최상위 타입이라는 말도 이제는 명확하게 이해가 될 것이다.

 

💪 타입 내로잉의 경우 추후에 2편에서 더욱 깊고 확장된 개념으로 다뤄보겠다!

Bottom 타입 

어떤 타입도 가지지 않는 최하위 집합을 의미하며 never 혹은 empty 를 뜻한다.

(빈 집합)`

값을 가질 수 없고 참조할 수 없는 타입, bottom타입에 그 어떠한 타입도 제공할 수 없다. (null, undefined도 불가능)

대부분의 타입스크립트 프로젝트는 never 타입을 사용하지 않는데 코드에서 불가능한 상태를 나타내기 위해 가끔 등장한다. 하지만 대부분 교차 타입을 잘못 사용한 실수일 확률이 높다. 

 

빈 집합은 최하위 집합이다. 집합에서 공집합은 모든 집합의 부분집합이다. 그만큼 제일 많은 외연에 둘러싸인 집합이라고 볼 수 있다. 이제 머릿속에서 시각화가 저절로 될 것이다. 

 

반환타입에서의 never

일부 함수는 값을 반환하지도 않고 반환할 생각도 없다. never는 의도적으로 항상 오류를 발생시키거나 무한 루프를 실행하는 함수이다. 

절대 반환하지 않는 함수를 위한것

 

반환타입에서의 void

void 타입은 함수의 반환 타입을 선언하는 데 사용하는 타입스크립트 키워드이다. void는 함수의 반환 타입이 무시된다는 것을 의미하고 undefined는 반환되는 리터럴 값이므로 서로 다르니 유의하자!

함수의 반환값이 자체적으로 반환될 수 있는 값도 아니고, 사용하기 위한 것도 아니라는 표시라고 생각하면 된다. 즉, 있는데 안쓸거고 궁금하지도 않다 혹은 없다를 포함한다.

아무것도 반환하지 않는 함수를 위한 것

 

TypeScript에서의 타입 시스템 Type System

타입 시스템은 프로그래밍 언어가 프로그램에서 가질 수 있는 타입을 이해하는 방법에 대한 규칙 집합이다. 

기본적으로 타입스크립트의 타입 시스템은 다음과 같이 작동한다.

 

  1. 코드를 읽고 존재하는 모든 타입과 값을 이해한다.
  2. 각 값이 초기 선언에서 가질 수 있는 타입을 확인한다. 분류 Classification 과정
  3. 각 값이 추후 코드에서 어떻게 사용될 수 있는지 모든 방법을 확인한다. 
  4. 값의 사용법(Operator)이 타입과 일치하지 않으면 사용자에게 오류를 표시한다. 

 

타입시스템에서 표시하는 오류 중 가장 자주 접하는 오류는 두 가지이다.

 

  • 구문 오류  - 타입스크립트가 자바스크립트로 변환되는 것을 차단한 경우.
  • 타입 오류 🌟

    타입스크립트의 타입 검사기가 프로그램의 타입에서 오류를 감지 했을 때 발생한다. 자바스크립트로 변환되는 것을 차단하지 않고 실행은 되지만 무언가 충돌하거나 예기치 않게 작동할 수 있음을 나타낸다.
    이는 주로 함수 호출이나 변수에 값을 제공할 수 있는지 여부를 확인하는 할당 가능성 Assignability 여부에 달려있다. 즉, 전달된 값이 예상된 타입으로 할당 가능한지 여부를 확인한다.

 

// 예시 1
let num: number;
let str: string;

num = 10;  // 할당 가능
str = num;  // 타입 오류: number를 string에 할당할 수 없음

// 예시 2
let value: any = "hello";

let strLength: number = (value as string).length;  // 할당 가능
let numLength: number = (value as number).length;  // 타입 오류: string 타입에는 length 속성이 없음

// 예시 3
interface Car {
    brand: string;
}

let car: Car = { brand: "Toyota" };  // 할당 가능
let vehicle: Car = { brand: "Honda", model: "Civic" };  // 타입 오류: Car 인터페이스에 정의된 프로퍼티 외에는 할당할 수 없음

 

 

타입을 선언한다는 것은 들어올 수 있는 값에 제한을 두는 것이다. number 타입을 선언했을 때 number라는 조건을 가진 내연이 생기는 것으로 상상할 수 있다. 조건에 맞지 않을 경우 오류를 뱉는 것. 

 

🤔💭

컴퓨터공학에서의 추상화 방식, 타입의 근원에 대해 알아보고 타입타입스크립트에서 다루는 기본 타입들에 대해 이해가 쉽도록 훑어보았다. 추후엔 타입스크립트가 이해한 값을 바탕으로 추론은 수행하는 방식들을 집합과 또 연관지어 알아보겠다. 

 

우선 오늘 이 포스팅을 통해 TypeScript가 가지는 의의와 JavaScript와의 차이를 확실히 알게 되었다. 이때까지 공부했던 여러 소스들을 나만의 방식으로 정의내려볼 수 있는 경험이었다. 흩어진 정보를 스스로 개념화 시킬 수 있었다 ◕‿◕ 이 포스팅을 보게된 분들도 조금 더 명확한 방식으로 이해할 수 있었으면 좋겠다.

 


[개발자의 일이관지]

[객체지향의 사실과 오해 📚]

[러닝 타입스크립트 📚]

[집합으로서의 타입]

CharGPT 🤖