본문 바로가기

CSE/개발

Type System of TypeScript

What is TypeScript?

TypeScript는 JavaScript에 타입 추론 기능을 추가한 언어로, MS에서 개발되었고 많은 분야에서 사용되고 있다. 기존의 JavaScript언어는 타입을 엄격하게 관리하지 않아 타입 관련 이슈들이 많았다 (프로그래머가 실수로 잘못 작성한 코드가 멀쩡히 실행되는 경우가 많아 디버깅이 어렵다). 이를 해결하기 위해 'typeof'함수를 이용해 방어적으로 프로그래밍을 해야 했는데, 코드가 굉장히 길어진다는 단점이 있었다. 그래서 이를 해결하고자 TypeScript가 등장했고, JavaScript의 모든 기능을 수행할 수 있으면서 TypeSystem을 도입해 compile time에 타입을 추론해 예상치 못한 에러를 잡아냈다.

Soundness

모든 언어에는 각자의 Type System이 있는데, 그들이 가지는 속성 중 가장 중요한 것은 단연 Soundness이다. 어떤 Type System이 Sound 하다는 것은 compile time에서 추론한 타입과 실제 실행시켰을 때 가지는 타입이 동일하다는 것을 의미한다. 즉, compile time에서 타입에러가 발견되지 않으면 프로그램을 실행시켰을 때도 타입 에러는 나지 않는다는 뜻이다. Sound한 Type System을 가지고 있는 언어는 대표적으로 Ocaml, Haskell 등이 있다.

 

그렇다면 Type System이 Sound 할 때 어떤 장단점이 있을까? 먼저, 가장 큰 장점은 Soundness의 정의에서도 알 수 있듯이 Runtime에 타입 문제로 에러가 날 일이 없다는 것이다. 간혹 Python으로 코딩을 하다 보면 '이 parameter가 무슨 타입이었지?'와 같은 생각을 할 때가 있는데, Ocaml로 코딩을 한다면 저런 생각을 하지 않아도 된다 (써보면 정말 편하다... Ocaml 짱!). 하지만 이렇게 엄격한 Type System은 간혹 귀찮음을 유발하기도 한다. 개발자의 시선에서는 분명 문제없이 도는 코드인데, Type System이 컴파일 에러를 내는 경우도 가끔 있다. 그리고 신선한 충격을 주기도 하는데, Ocaml에서는 아래와 같은 코드는 컴파일 에러를 낸다.

 

let a = 1.0 + 2.0

 

그 이유는 '+' 연산자는 정수만을 더하는 연산자이기 때문에 실수에서의 더하기인 '+.'을 사용해야 한다. (참고)

이런 단점에도 불구하고 Sound한 Type System은 강력한 힘을 가짐에는 이견이 없다 (Ocaml로 코딩하다가 Python으로 코딩하려면 힘들다).

Type System of TypeScript

그렇다면 TypeScript의 TypeSystem은 Sound 할까? 그렇지 않다. 공식 Document에 의하면 JavaScript의 기능을 모두 가져오기 위해 unsound 하게 설계되었다고 한다 [1]. 그렇다면 TypeScript에서는 어떤 방식으로 동작할까? 우선, Number(int), string 등의 타입에 대해서는 우리가 알고 있는 방식으로 동작한다. 그래서 아래와 같은 코드를 compile time에 잡아준다.

 

let a: number = "123";

 

하지만 Object Type의 경우는 두 타입이 Exact 하게 똑같은지 확인하는 것이 아니라 Compatible 한 지 확인한다 [1]. 이때 두 타입이 Compatible 하다는 것은 attribute가 포함관계를 이룬다는 것이다. 아래 예시를 보자. (코드 출처: [1])

 

interface Pet {
  name: string;
}
let dog = { name: "Lassie", owner: "Rudd Weatherwax" };
function greet(pet: Pet) {
  console.log("Hello, " + pet.name);
}
greet(dog); // OK

let x = (a: number) => 0;
let y = (b: number, s: string) => 0;
y = x; // OK
x = y; // Error

 

분명 greet의 parameter의 타입은 Pet이지만 parameter로 pass 된 dog에도 Pet가 가지는 "name"이라는 attribute가 있으므로 에러가 나지 않는다. 또한, 아래쪽의 예시에서는 함수 x와 y는 parameter가 달라 타입이 다르지만 y가 x의 superset(parameter 기준)이므로 y에 x를 대입할 수 있다. 하지만 반대는 안된다. 

 

이러한 Type Checking방식을 Structural Typing(Duck Typing)이라고 한다. 즉, 구조만 같으면 같은 타입으로 인식하는 것이다. 반면, 타입 이름을 기준으로 판별하는 방식을 Nominal Typing이라고 한다. C/C++/Java/Ocaml 등이 이러한 방식을 사용하고 있다.

 

개인적으로, TypeScript가 이런 Type System을 가지는지 몰랐다. Sound하지 않다는 것은 알았지만 그래도 Strict 하다고 생각했고, 당연히 Nominal Typing방식을 따를 줄 알았다. 하지만 분명히 내가 실수한 코드인데도 컴파일 에러가 나지 않는 것을 보고 의아했고, 그 이유를 알아보면서 이 글을 작성하게 되었다. 아직 적응이 되지 않는 방식이지만 "로마에서는 로마법을 따라라"라는 말이 있듯이 TypeScript를 사용하는 동안에는 적응해보려고 노력해봐야겠다.

ReScript

만약 TypeScript의 Type System에 만족하지 못했지만 JavaScript의 기능을 이용하고 싶다면 이 언어를 사용해보자. Rescript는 Sound한 Type System을 가지는 언어이고, 최종적으로 JavaScript로 컴파일되어 JavaScript의 기능을 이용할 수 있다. 비록 새로 나온 언어이고, 위에서 언급한 것처럼 Type System이 Sound한 대신 JavaScript의 모든 기능을 다 담을 수는 없어 아직 많은 사람들이 사용하지 않지만 Sound 한 Type System은 정말 강력한 무기이기 때문에 언젠가는 빛을 발할 거라고 생각한다. 그리고 실제로 사용해보면 정말 편하다.

References

[1] https://www.typescriptlang.org/docs/handbook/type-compatibility.html

'CSE > 개발' 카테고리의 다른 글

Regex Quantifier 사용시 주의할 점  (0) 2022.09.13
새로고침이 안된다면?  (0) 2022.09.11
Python Decorator  (0) 2022.09.05