
먼저 명목적 타이핑이라는 것이 무엇일까? 명목... 얼핏 들으면 이름이 굉장히 중요한 역할을 하는 타이핑처럼 보인다.
실제로 명목적으로 구체화한 타입 시스템에서는 값이나 객체의 타입은 이름으로 구분된다.
명목적으로 구체화한 타입 시스템 타입을 사용하는 여러 프로그래밍 언어에서 값이나 객체는 하나의 구체적인 타입을 가지고 있다. 타입은 이름으로 구분되며 컴파일타임 이후에도 남아있는데, 이것을 명목적으로 구체화한 타입 시스템이라고 부르기도 한다.
명목적 타이핑을 사용하는 언어들의 예시에는 Java, C++ 등이 있다.
class Animal {
String name;
int age;
}
class Plant {
String species;
// 추가적인 속성...
}
Animal myAnimal = new Animal(); // Animal 타입의 객체
Plant myPlant = new Plant(); // Plant 타입의 객체
// 컴파일 에러: Animal과 Plant는 명목적으로 서로 다른 타입이므로 호환되지 않음
myAnimal = myPlant;
예를 들어, Animal 클래스는 name과 age라는 속성을 가진 타입이다. 이 클래스는 그 자체의 이름, 즉 Animal에 의해 정의되고 구별된다.
또한, 서로 다른 클래스끼리 명확한 상속 관계나 공통으로 가지고 있는 인터페이스가 없다면 타입은 서로 호환되지 않는다.
타입스크립트는 구조적 타이핑을 따른다. 이 구조적 타이핑이라는 것은 타입을 판단할 때 타입의 이름으로 판단하는 것이 아니라 객체의 내부 속성들이나 값의 내부 구조, 그 속성들의 타입 구조를 파악하는 것을 의미한다.
interface Developer {
faceValue: number;
}
interface BankNote {
faceValue: number;
}
let developer: Developer = { faceValue: 52 };
let bankNote: BankNote = { faceValue: 10000 };
developer = bankNote; // OK
bankNote = developer; // OK
예를 들어, Developer와 BankNote 인터페이스는 모두 faceValue라는 같은 속성을 가지고 있다.
이 두 타입은 서로 다른 이름을 가지고 있지만, 객체 내부는 구조적으로 동일하기 때문에 타입스크립트 같은 구조적 타입 시스템을 사용하는 언어에서는 서로 호환될 수 있다.
구조적 타이핑은 알겠는데 구조적 서브 타이핑은 무엇일까... 서브... 뭔가의 하위같은 느낌이 강하게 온다.
구조적 서브타이핑(structural subtyping)은 어떤 타입이 다른 타입의 하위 타입이 되기 위해서는 해당 타입이 필요로 하는 구조(속성 및 메서드)를 가지고 있어야 한다는 원칙을 뜻한다.
구조적 서브타이핑은 타입스크립트에서 타입을 구분하는 중요한 개념으로, 객체가 가진 속성을 바탕으로 타입을 구분하며 이를 통해 타입 간의 호환성을 구조적으로 판단하고, 타입 계층 구조에 구애 받지 않는 유연한 타입 시스템을 구현할 수 있다.
interface Pet {
name: string;
}
interface Cat {
name: string;
age: number;
}
let pet: Pet;
let cat: Cat = { name: "Zag", age: 2 };
// ✅ OK
pet = cat;
Cat은 Pet과 서로 완벽하게 일치하는 구조를 이루지는 않지만, Pet이 갖고 있는 name이라는 속성을 가지고 있기 때문에 Cat 타입의 변수를 Pet 타입의 변수에 할당할 수 있다.
여기에서 캡틴판교 강의의 설명에 따르면 오른쪽에 있는 것이 왼쪽에 있는것보다 내부 구조나 규모가 더 커야 할당이 가능하다.
interface Pet {
name: string;
}
let cat = { name: "Zag", age: 2 };
function greet(pet: Pet) {
console.log(`Hello, ${pet.name}`);
}
// 함수에서는 pet이라는 매개변수의 타입으로 Pet을 지정
// 실제로는 Cat 타입의 cat이라는 변수가 들어감
greet(cat); // ✅ OK
greet() 함수의 매개변수에 들어갈 수 있는 값은 Pet 타입으로 제한되어 있어도, Cat타입의 객체를 인자로 전달할 수 있다.
cat 객체는 Pet 인터페이스가 가지고 있는 name 속성을 가지고 있어 pet.name의 방식으로 name 속성에 접근할 수 있기 때문이다.
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
class Developer {
name: string;
age: number;
sleepTime: number;
constructor(name: string, age: number, sleepTime: number) {
this.name = name;
this.age = age;
this.sleepTime = sleepTime;
}
}
function greet(p: Person) {
console.log(`Hello, I'm ${p.name}`);
}
const developer = new Developer("zig", 20, 7);
greet(developer); // Hello, I'm zig
Developer 클래스가 Person 클래스를 상속받지 않아도 Person이 갖고 있는 name과 age 속성을 가지고 있기 때문에 greet() 함수에 Developer 객체를 인자로 전달할 수 있다.
"만약 어떤 새가 오리처럼 걷고, 헤엄치며 꽥꽥거리는 소리를 낸다면 나는 그 새를 오리라고 부를 것이다"
명목적 타이핑은 객체의 속성을 다른 객체의 속성과 호환되지 않도록 하여 안전성을 추구한다. 따라서 매우 엄격하다고 볼 수 있다.
하지만 타입스크립트는 덕 타이핑을 기반으로하는 자바스크립트를 모델링한 언어이기 때문에 구조적 타이핑을 채택했다.
덕 타이핑은 어떤 타입에 부합하는 변수와 메서드를 가질 경우 해당 타입에 속하는 것으로 간주하는 방식이다.
타입스크립트는 이 특징을 받아들여 더욱 유연한 타이핑을 제공하며 쉬운 사용성과 안정성을 동시에 추구한다.
두 가지 타이핑 방식은 모두 객체의 변수, 메서드 같은 필드를 기반으로 타입을 검사하지만 다음과 같은 차이점을 가진다.
덕 타이핑
구조적 타이핑
물론 타입스크립트의 구조적 서브 타이핑의 특징때문에 예기치 못한 결과가 나올 때도 있다.
interface Cube {
width: number;
height: number;
depth: number;
}
function addLines(c: Cube) {
let total = 0;
for (const axis of Object.keys(c)) {
// 🚨 Element implicitly has an 'any' type
// because expression of type 'string' can't be used to index type 'Cube'.
// 🚨 No index signature with a parameter of type 'string'
// was found on type 'Cube'
const length = c[axis];
total += length;
}
}
Cube 인터페이스의 모든 필드는 number 타입을 가지지만, c라는 매개변수에 들어올 객체는 Cube의 width, height, depth 외에도 어떤 속성이든 가질 수 있기 때문에 c[axis]의 타입이 string일 수도 있어 에러가 발생한다.
const namedCube = {
width: 6,
height: 5,
depth: 4,
name: "SweetCube", // string 타입의 추가 속성이 정의되었다
};
addLines(namedCube); // ✅ OK
이처럼 타입스크립트는 c[axis]가 어떤 속성을 지닐지 알 수 없으며 c[axis]의 타입을 number라고 확정할수 없어서 에러를 발생시킨다. 구조적 타이핑의 특징으로 Cube타입 값이 들어갈 곳에 name같은 추가 속성을 가진 객체도 할당할 수 있기 때문에 발생하는 문제이다.
이러한 한계를 극복하기 위해 타입스크립트는 유니온같은 방법을 도입했다.
참조
스터디 Github: https://github.com/Coding-Village-Protector/woowahan-ts
오리 사진: Freepik