노트

공변성과 반공변성

Covariance and Contravariance

CS#ts · 연결된 개념 5개

쉽게 말하면

공변성과 반공변성은 함수를 대신 끼워도 안전한지 따지는 규칙이에요. 모든 동물을 보는 수의사는 개 병원 자리에 앉혀도 되지만 반대는 안 돼요. 이처럼 매개변수는 거꾸로, 반환값은 같은 방향으로 맞춰요.

비유가 깨지는 곳 TS는 이 규칙을 다 지키지 않아요. 배열은 공변이라 Dog[]로 만든 걸 Animal[]로 받아 Cat을 넣을 수 있고, 메서드 문법으로 선언한 매개변수는 이변적으로 느슨하게 검사돼요.

공변성(Covariance)과 반공변성(Contravariance)은 Dog가 Animal의 서브타입일 때, 그 타입을 감싼 타입(배열, 함수)의 서브타입 관계가 같은 방향으로 유지되는지(공변) 아니면 뒤집히는지(반공변)를 말한다.

// 반환 타입은 공변: Dog를 돌려주는 함수는 Animal 팩토리로 쓸 수 있다
let makeAnimal: () => Animal = (): Dog => new Dog()
 
// 매개변수는 반공변: Animal을 처리하는 함수는 Dog 핸들러 자리에 들어갈 수 있다
let handleDog: (d: Dog) => void = (a: Animal) => {}
let handleAnimal: (a: Animal) => void = (d: Dog) => {}  // ❌ strictFunctionTypes
  • 직관: 더 많은 것을 받아들이고(매개변수), 더 구체적인 것을 돌려주는(반환) 함수가 더 안전한 대체품이다. 리스코프 치환 원칙(리스코프 치환 원칙)을 함수 타입에 적용한 것이다
  • 콜백·이벤트 핸들러를 넘길 때 이 규칙이 그대로 나온다. Event를 처리하는 리스너는 MouseEvent 리스너 자리에 넣을 수 있지만, 반대는 안 된다
  • strictFunctionTypes(strict에 포함)를 켜야 함수 프로퍼티의 매개변수가 반공변으로 검사된다. 메서드 문법(handle(x: T): void)으로 선언한 것은 여전히 이변적(Bivariant)으로 느슨하게 검사된다
  • 배열은 공변이다(Dog[]를 Animal[]에 넣을 수 있다). 사실 이건 불건전(unsound)하다. Animal[]로 받은 쪽이 Cat을 push하면 원래 Dog[]가 오염된다. TS가 편의를 위해 허용한 구멍이고, readonly Animal[]로 받으면 이 문제가 사라진다

기초는 구조적 타이핑, 함수 타입 표기는 함수 오버로드와 시그니처.

연결된 개념

이 노트를 가리키는 문서

뜻이 가까운 노트

  • 기본형 집착

    전화번호·금액·우선순위 같은 도메인 개념을 끝까지 문자열이나 숫자로만 다루는 냄새. 같은 검증과 포맷 코드가 여기저기 반복되고, 문자열로 모든 걸 표현하는 "stringly typed" 코드가 된다.

  • 매개변수 객체 만들기

    여러 함수에 늘 함께 넘겨지는 값 묶음(데이터 뭉치, Data Clumps)을 하나의 객체로 묶어 넘기는 리팩터링.

  • 조건부 로직을 다형성으로 바꾸기

    타입에 따라 갈라지는 조건문을, 타입마다 클래스(또는 객체)를 두고 각자 자기 방식으로 처리하게 바꾸는 리팩터링.

  • 의존성 역전 원칙

    고수준 모듈(업무 규칙)이 저수준 모듈(데이터베이스(DB), HTTP(Hypertext Transfer Protocol) 클라이언트 같은 세부 구현)에 직접 의존하지 않고, 둘 다 추상화에 의존해야 한다는 원칙. 의존 방향이 "업무 → 세부"에서 "세부 → 추상화 ← 업무"로 뒤집힌다.

  • 변수·컬렉션 캡슐화

    넓은 범위에서 쓰이는 데이터에 직접 접근하지 못하게 하고, 읽고 쓰는 함수를 통해서만 다루게 하는 리팩터링. 데이터를 옮기거나 바꿀 때 고쳐야 할 곳이 접근 함수 한 곳으로 좁아진다.

보기 옵션