[typescript] 가변성 문제와 반공변성의 개념

Typescript는 정적 타입 시스템을 갖춘 JavaScript의 확장된 언어로, 더 안정적이고 유지보수가 쉬운 코드를 작성할 수 있도록 도와줍니다. 그러나 제네릭 타입과 관련하여 가변성과 반공변성을 다룰 때 이슈가 발생할 수 있습니다. 이러한 문제를 처음 경험하는 경우 혼란스러울 수 있으므로, 가변성 문제와 반공변성의 개념을 자세히 살펴보겠습니다.

가변성이란 무엇인가?

제네릭과 관련하여 가변성은 타입이 어떻게 변하는지를 의미합니다. 가변성은 크게 세 가지 범주로 나뉩니다.

불변성 (Invariance)

어떤 타입에서, 하위 타입과 상위 타입 사이의 관계가 없는 경우를 의미합니다. 즉, 서로 다른 두 타입 간에는 할당이 불가능합니다.

공변성 (Covariance)

타입 A가 타입 B의 하위 타입인 경우, 타입 List는 List의 하위 타입이 되는 경우를 의미합니다. 이는 데이터를 읽을 수는 있지만 쓸 수는 없는 상황이 발생할 수 있습니다.

class Animal {}
class Dog extends Animal {}

let list1: Array<Animal> = [new Animal(), new Animal()];
let list2: Array<Dog> = [new Dog(), new Dog()];

list1 = list2; // 에러 발생

반공변성 (Contravariance)

타입 A가 타입 B의 하위 타입인 경우, 타입 List는 List의 하위 타입이 되는 경우를 의미합니다. 이는 데이터를 쓸 수는 있지만 읽을 수는 없는 상황이 발생할 수 있습니다.

반공변성의 사용 사례

반공변성은 제네릭 타입의 매개변수를 가지는 함수와 인터페이스를 다룰 때 주로 발생합니다. 예를 들어, compare 함수가 두 개의 제네릭 타입을 받아 비교하는 경우, 하나는 읽기만 가능하고 다른 하나는 쓰기만 가능할 수 있습니다.

interface Comparator<T> {
  compare: (a: T, b: T) => number;
}

function sort<T>(list: T[], comparator: Comparator<T>) {
  // sorting logic
}

let animalComparator: Comparator<Animal> = {
  compare: (a, b) => {
    // compare logic
  }
};

let dogComparator: Comparator<Dog> = {
  compare: (a, b) => {
    // compare logic
  }
};

let dogs: Dog[] = [new Dog(), new Dog()];
sort(dogs, animalComparator); // 에러 발생
sort(dogs, dogComparator); // 정상 동작

결론

가변성 문제와 반공변성은 제네릭 타입과 관련하여 타입 호환성과 안정성을 고려할 때 중요한 요소입니다. 적절한 상황에서 반공변성을 활용하여 유연하고 안정적인 프로그램을 작성할 수 있습니다.

이러한 개념을 이해하고 적용함으로써, Typescript 코드의 가독성과 안정성을 향상시킬 수 있습니다. Typescript의 가변성 문제와 반공변성의 개념에 대해 더 알아보고자 한다면, 공식 Typescript 문서를 참고하는 것이 좋습니다.

Typescript 공식 문서

이상으로 Typescript의 가변성 문제와 반공변성의 개념에 대해 살펴보았습니다. 앞으로도 Typescript를 더 깊게 이해하기 위해 관련 주제에 대해 추가적으로 공부하시기 바랍니다.