1. Covariance, Contravariance, Invariance πŸ‘©β€πŸ’»

ν”„λ‘œκ·Έλž˜λ° μ–Έμ–΄μ—μ„œ 각 νƒ€μž…κ°„μ˜ μ°Έμ‘° 관계가 μ—°κ΄€λœ 것듀이 λ§Žλ‹€. Classes 의 상속 관계, Generics 에 μ˜ν•œ 관계, Array 와 같은 Monad μ»¨ν…Œμ΄λ„ˆ, ν•¨μˆ˜μ˜ Parameters 와 Return λ“± μ—¬λŸ¬ κ³³μ—μ„œ μ΄λŸ¬ν•œ 관계가 μ‘΄μž¬ν•œλ‹€. μ›λž˜ μ§€μ •λœ 관계보닀 더 νŒŒμƒλ˜κ±°λ‚˜ 덜 νŒŒμƒλ˜λŠ” 관계 λ˜λŠ” μ΄λŸ¬ν•œ 관계λ₯Ό 갖지 μ•ŠλŠ” 독립적인 관계듀이 μ‘΄μž¬ν•  수 μžˆλŠ”λ° 이것듀에 λŒ€ν•œ μ •μ˜λ₯Ό λ‚˜νƒ€λ‚΄λŠ” 것이닀.

μˆ˜ν•™μ—μ„œ μ •μˆ˜ 와 μžμ—°μˆ˜ 의 λ‹€μ΄μ–΄κ·Έλž¨μ„ λ– μ˜¬λ €λ³΄μž. μ •μˆ˜λŠ” μžμ—°μˆ˜λ₯Ό ν¬ν•¨ν•˜κΈ° λ•Œλ¬Έμ— μ •μˆ˜μ˜ λ‹€μ΄μ–΄κ·Έλž¨ μ•ˆμ— μžμ—°μˆ˜κ°€ μ†ν•œλ‹€. μ—¬κΈ°μ„œ μ •μˆ˜λŠ” μƒμœ„ νƒ€μž…μ΄κ³ , μžμ—°μˆ˜λŠ” ν•˜μœ„ νƒ€μž…μ΄λ‹€. ν”„λ‘œκ·Έλž˜λ°μ— μžˆμ–΄μ„œλ„ μ΄λŸ¬ν•œ 관계가 μžˆλŠ”λ° κ°€μž₯ λŒ€ν‘œμ μ΄κ³  μ‰½κ²Œ μ ‘ν•  수 μžˆλŠ” 게 λ°”λ‘œ Classes 의 상속 관계닀.

λ‹¨μˆœνžˆ νƒ€μž…μœΌλ‘œλ§Œ μƒκ°ν•΄λ³΄μž. νƒ€μž… A와 Bκ°€ 있고, I<U> ν‘œκΈ°λŠ” Type Argument Uλ₯Ό κ°–λŠ” Type Constructor Iλ₯Ό λ‚˜νƒ€λ‚Έλ‹€.

  • Covariance: A β‰₯ BμΌλ•Œ, λ™μΌν•˜κ²Œ I<A> β‰₯ I<B>이닀.
  • Contravariance: A β‰₯ BμΌλ•Œ, λ°˜λŒ€λ‘œ I<A> ≀ I<B>이닀.
  • Bivariance: Covariance λ©΄μ„œ λ™μ‹œμ— Contravariance 이닀. 즉, A β‰₯ BμΌλ•Œ, I<A> = I<B>이닀.
  • Variance: Covariance λ˜λŠ” Contravariance λ˜λŠ” Bivariance 이닀.
  • Invariance: Non-Variance 이닀.

co-: β€˜ν•¨κ»˜β€™μ˜ λœ»μ„ λ‚˜νƒ€λ‚Έλ‹€. 즉, Origin κ³Ό 같은 λ°©ν–₯으둜 νƒ€μž… μ°Έμ‘° νŠΉμ„±μ„ λ‚˜νƒ€λ‚Έλ‹€.
contra-: β€˜λ°˜λŒ€β€™μ˜ λœ»μ„ λ‚˜νƒ€λ‚Έλ‹€. 즉, Origin κ³Ό λ°˜λŒ€ λ°©ν–₯으둜 νƒ€μž… μ°Έμ‘° νŠΉμ„±μ„ λ‚˜νƒ€λ‚Έλ‹€.


이 쀑 μš°λ¦¬κ°€ νƒ€μž…μ„ ꡬ뢄할 λ•Œ μ΄μ•ΌκΈ°ν•˜λŠ” 것은 Invariance, Covariance, Contravariance 3κ°€μ§€λ‘œ μ’€ 더 ν”„λ‘œκ·Έλž˜λ° κ΄€μ μ—μ„œ μ΄μ•ΌκΈ°ν•΄λ³΄μž.

  • Covariance: μ›λž˜ μ§€μ •λœ 것보닀 더 νŒŒμƒλœ νƒ€μž…(more derived type)을 μ‚¬μš©ν•  수 μžˆλ‹€.
  • Contravariance: μ›λž˜ μ§€μ •λœ 것보닀 덜 νŒŒμƒλœ νƒ€μž…(less derived type)을 μ‚¬μš©ν•  수 μžˆλ‹€.
  • Invariance: μ›λž˜ μ§€μ •λœ κ²ƒλ§Œ μ‚¬μš©ν•  수 μžˆλ‹€.

Covariant Type ParametersλŠ” Polymorphismκ³Ό 맀우 μœ μ‚¬ν•œ 할당을 μ‚¬μš©ν•  수 μžˆλ‹€.

일반적으둜 μž…λ ₯ μœ„μΉ˜μ— ν•΄λ‹Ήν•˜λŠ” Parameter Type 은 Invariantν•˜κ±°λ‚˜ Covariantν•˜κ³ , 좜λ ₯ μœ„μΉ˜μ— ν•΄λ‹Ήν•˜λŠ” Return Type 은 Covariantν•˜λ‹€. ν•˜μ§€λ§Œ 이것은 각 μ–Έμ–΄λ§ˆλ‹€ μ„œλ‘œ λ‹€λ₯Έ νŠΉμ§•μ„ λ³΄μœ ν•˜κ³  있기 λ•Œλ¬Έμ— λ°˜λ“œμ‹œ κ·ΈλŸ¬ν•œ 것은 μ•„λ‹ˆλ‹€. 예λ₯Ό λ“€μ–΄ Scala λŠ” Parameter Type κ°€ Contravariant ν•˜κ³ , Eiffel 은 Covariant ν•˜λ‹€.

Β  Type Relation TypeScript 4.7 Syntax
Origin A β‰₯ B Β 
Covariance I<A> β‰₯ I<B> <out T>
Contravariance I<A> ≀ I<B> <in T>
Invariance I<A>, I<B> λŠ” 독립적이닀. <in out T>


Classes 의 κΈ°λ³Έ λ™μž‘μ€ 더 νŒŒμƒλœ νƒ€μž…(more derived type) Dogλ₯Ό 더 큰 μ»¨ν…Œμ΄λ„ˆ Animal에 넣을 수 μžˆλ‹€. 이것을 subtyping이라 λΆ€λ₯Έλ‹€.

DogλŠ” Animal의 subtype 이고 ν™”μ‚΄ν‘œλ₯Ό μ΄μš©ν•΄ λ‹€μŒκ³Ό 같이 ν‘œν˜„ν•  수 μžˆλ‹€.
Dog β†’ Animal 이것을 subtyping direction 이라 ν•œλ‹€.

I<Dog> β†’ I<Animal>와 같이 μ»¨ν…Œμ΄λ„ˆλ‘œ λž˜ν•‘ν•œ νƒ€μž…μ΄ Origin νƒ€μž…μ˜ subtyping direction κ³Ό λ™μΌν•œ λ°©ν–₯을 κ°–λŠ” κ±Έ Covariance, λ°˜λŒ€ λ°©ν–₯을 κ°–λŠ” κ±Έ Contravariance라 ν•œλ‹€.


2. Variance in TypeScript πŸ‘©β€πŸ’»

1. Origin Types

class Animal {
  private species: string;

  constructor(species: string) {
    this.species = species;
  }
}

class Dog extends Animal {
  private name: string;

  constructor(species: string, name: string) {
    super(species);
    this.name = name;
  }
}

const animal: Animal = new Dog('강아지', 'ν˜Έλ‘')
interface Animal {
  species: string
}

interface Dog extends Animal {
  name: string
}

const animal: Animal = {
  species: '강아지',
  name: 'ν˜Έλ‘'
} as Dog
type Animal = {
    species: string
}

type Dog = Animal & {
    name: string
}

const animal: Animal = {
    species: '강아지',
    name: 'ν˜Έλ‘'
} as Dog

TypeScript λŠ” JavaScript 둜 λŒ€μ²΄λ˜μ–΄μ•Ό ν•˜κ³ , 사싀상 Classes κ°€ μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ”λ‹€. νƒ€μž…μ„ μƒμ„±ν•˜κ³  μƒμ†ν•˜λŠ” 방법은
class, interface, type 외에도 Constructor Function 을 μ‚¬μš©ν•˜κ±°λ‚˜ Object Literal, Closures λ₯Ό μ‚¬μš©ν•˜λŠ” λ“± λ‹€μ–‘ν•œ 방법이 μžˆμœΌλ‚˜ μ „λΆ€ ν•¨μˆ˜λ₯Ό μ΄μš©ν•œ Object 객체λ₯Ό μƒμ„±ν•œλ‹€. λ”°λΌμ„œ μ–΄λ–€ 방법을 μ‚¬μš©ν•˜λ“  Dog β†’ Animal의 subtyping direction 을 κ°–λŠ”λ‹€.

이번 μ„Ήμ…˜μ—μ„œλŠ” 객체λ₯Ό ν‘œν˜„ν•  λ•Œ κ°€μž₯ 일반적으둜 μ‚¬μš©λ˜λŠ” interfaceλ₯Ό μ‚¬μš©ν•΄ νƒ€μž…μ„ ν‘œν˜„ν•œλ‹€.

2. TypeScript 4.7

TypeScript λŠ” 4.7 버전뢀터 in, out modifier λ₯Ό μ§€μ›ν•œλ‹€. μ—†λ˜ κΈ°λŠ₯이 μΆ”κ°€λœ 것은 μ•„λ‹ˆκ³  κΈ°μ‘΄μ—λŠ” μ½”λ“œλ₯Ό μž‘μ„±ν•˜λ©΄ μ»΄νŒŒμΌλŸ¬κ°€ Covariance 인지, Contravariance 인지 μΆ”λ‘ ν•˜λ˜ 것을 μ‚¬μš©μžκ°€ λͺ…μ‹œμ μœΌλ‘œ μž‘μ„±ν•  수 μžˆλ„λ‘ modifier κ°€ λ„μž…λœ 것이닀. 이 modifier κ°€ λ„μž…λœ μ΄μœ λŠ” μ½”λ“œλ₯Ό λ³΄λŠ” 것 만으둜 μ‰½κ²Œ μœ ν˜•μ„ 이해할 수 μžˆλ„λ‘ μ‚¬μš©μžμ—κ²Œλ„ 도움을 μ£Όκ³ , 컴파일러의 속도와 정확성을 λ†’μ΄λŠ” κΈ°λŠ₯을 ν•˜κΈ° μœ„ν•¨μ΄λ‹€.

type Getter<T> = () => T
type Setter<T> = (value: T) => void

λ₯Ό λͺ…μ‹œμ μœΌλ‘œ ν‘œν˜„ν•˜λ©΄

type Getter<out T> = () => T
type Setter<in T> = (value: T) => void

이 λœλ‹€. λ§ˆμ°¬κ°€μ§€λ‘œ

interface State<T> {
  get: () => T
  set: (value: T) => void
}

λ₯Ό λͺ…μ‹œμ μœΌλ‘œ ν‘œν˜„ν•˜λ©΄

interface State<in out T> {
  get: () => T
  set: (value: T) => void
}

in, out modifier κ°€ μ˜λ―Έν•˜λŠ” 것은 Generic Tλ₯Ό μ–΄λ–»κ²Œ μ‚¬μš©λ˜λŠ”μ§€λ₯Ό λͺ…μ‹œν•˜λŠ” 것이닀. λ”°λΌμ„œ Getter 에 type Getter<in T> = () => T둜 μž‘μ„±ν•˜λ©΄ compile-time error κ°€ λ°œμƒλœλ‹€. 반면 type Getter<in out T> = () => TλŠ” ν˜Όλ™μ„ 쀄 μˆ˜λŠ” μžˆμœΌλ‚˜ μ»΄νŒŒμΌμ— λ¬Έμ œκ°€ λ˜μ§€λŠ” μ•ŠλŠ”λ‹€.

μ–΄μ¨Œλ“  Tλ₯Ό input 으둜 μ‚¬μš©ν•  λ•ŒλŠ” in modifier λ₯Ό, output 으둜 μ‚¬μš©ν•  λ•ŒλŠ” out modifier λ₯Ό μ‚¬μš©ν•΄μ•Όν•œλ‹€. 그리고 λŒ€λΆ€λΆ„μ˜ TypeScript μ‹œμŠ€ν…œμ—μ„œ in은 Contravariant ν•˜λ©°, out은 Covariant ν•œ νŠΉμ„±μ„ κ°–λŠ”λ‹€.

그런데 in out으둜 μž‘μ„±ν•˜λ©΄ μ–΄λ–»κ²Œ 될까? input κ³Ό output λͺ¨λ‘ Tλ₯Ό μ‚¬μš©ν•˜κ² λ‹€λŠ” κ²ƒμ΄λ―€λ‘œ Invariant ν•˜λ‹€.

3. Covariance

const dogArray: Array<Dog> = []
const animalArray: Array<Animal> = dogArray

Array μ»¨ν…Œμ΄λ„ˆλ‘œ λž˜ν•‘ν•œ Dog λŠ” μ—¬μ „νžˆ Array μ»¨ν…Œμ΄λ„ˆλ‘œ λž˜ν•‘ν•œ Animal 의 subtype 이닀.

[Dog] β†’ [Animal]

Swift 의 Array λŠ” Origin Types 의 subtyping direction κ³Ό λ™μΌν•œ λ°©ν–₯ κ°–λŠ” Covariance λ‹€.


Covariance 의 또 λ‹€λ₯Έ μ˜ˆλŠ” Function Retun Typesλ‹€.

const dogBuilder: () => Dog = () => ({} as Dog)
const animalBuilder: () => Animal = dogBuilder

Dog β†’ Animalκ³Ό λ™μΌν•œ λ°©ν–₯ () -> Dog β†’ () -> Animalμ΄λ―€λ‘œ ν•¨μˆ˜μ˜ Return Types λŠ” Covarianceλ‹€.

4. Contravariance

그런데 Function Parameter Typesμ—μ„œλŠ” Origin Types 와 λ‹€λ₯Έ λ°©ν–₯을 κ°–λŠ”λ‹€.

const dogHandler: (_: Dog) => void = (dog) => console.log(dog)
const animalHandler: (_: Animal) => void = dogHandler   // error

Dog β†’ Animalμ΄μ§€λ§Œ (Dog) -> void β†’ (Animal) -> voidλŠ” ν—ˆμš©λ˜μ§€ μ•ŠλŠ”λ‹€. 이것은 Type Unsafe ν•˜κΈ° λ•Œλ¬Έμ΄λ‹€.

const animal: Animal = {} as Dog
const animalArray: Animal[] = [{} as Dog, {} as Dog, {} as Dog]

Covariant ν•œ μœ„ μ½”λ“œλ₯Ό 보자. 이것이 ν—ˆμš©λ˜λŠ” 것은 Animal μ΄λΌλŠ” μ»¨ν…Œμ΄λ„ˆκ°€ 더 크기 λ•Œλ¬Έμ— Dog λ₯Ό μ €μž₯ν•˜λŠ” 것이 κ°€λŠ₯ν•˜κΈ° λ•Œλ¬Έμ΄λ‹€.

const dogBuilder: () => Dog = () => ({} as Dog)
const animalBuilder: () => Animal = dogBuilder

λ§ˆμ°¬κ°€μ§€λ‘œ, Function Return TypesλŠ” μ–΄λ”˜κ°€μ— μ €μž₯ν•˜κΈ° μœ„ν•¨μ΄λΌλŠ” 것을 κΈ°μ–΅ν•˜μž. μ• μ΄ˆμ— const animal: Animal = {} as Dog μ½”λ“œλŠ” Object 생성을 Class Syntax λ₯Ό μ‚¬μš©ν•΄ μž‘μ„±ν–ˆμ„ 경우, constructorλ₯Ό ν˜ΈμΆœν•˜λŠ” μ½”λ“œμ΄κ³ , 이것은 Class Instances λ₯Ό 생성해 λ°˜ν™˜ν•˜λŠ” νŠΉμˆ˜ν•œ ν•¨μˆ˜μ΄λ‹€. 예λ₯Ό λ“€μ–΄ Classes λ₯Ό Singleton으둜 λ§Œλ“€μ–΄ Instances λ₯Ό μƒμ„±ν•˜λŠ” λ‹€λ₯Έ ν•¨μˆ˜λ₯Ό λ§Œλ“€μ–΄ μ‚¬μš©ν•œλ‹€κ³  이 κ·œμΉ™μ΄ κΉ¨μ§„λ‹€λŠ” 것이 말이 μ•ˆ λ˜λŠ” 것이닀. 즉, Function Return Types λŠ” μ–΄λ”˜κ°€ μ €μž₯되기 μœ„ν•¨μ΄κ³ , λ‹Ήμ—°νžˆ 더 큰 μ»¨ν…Œμ΄λ„ˆλ‘œ λ‚΄λ³΄λ‚΄λŠ” 것이 κ°€λŠ₯ν•΄μ•Ό ν•œλ‹€.

ν•˜μ§€λ§Œ Function Parameter Types의 κ²½μš°λŠ” λ‹€λ₯΄λ‹€! 이것은 μ–΄λ”˜κ°€μ— μ €μž₯되기 μœ„ν•¨μ΄ μ•„λ‹Œ, Functions 의 Body λ‚΄μ—μ„œ μ–΄λ– ν•œ λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μ„ μˆ˜ν–‰ν•˜κΈ° μœ„ν•¨μ΄λ‹€. 즉, Dog μ—λ§Œ μ‘΄μž¬ν•˜λŠ” 것듀을 ν•„μš”λ‘œ ν•œλ‹€λŠ” 것인데, λ§Œμ•½ Animal 이 ν—ˆμš©λ˜μ–΄ 또 λ‹€λ₯Έ ν•˜μœ„ νƒ€μž…μΈ Cat 이 λ“€μ–΄μ˜¬ 경우, λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μ€ μˆ˜ν–‰ν•  수 μ—†κ²Œ 되고, μ—λŸ¬λ₯Ό λ°œμƒμ‹œν‚¨λ‹€.

const animalHandler: (_: Animal) => void = (animal) => console.log(animal)
const dogHandler: (_: Dog) => void = (dog) => animalHandler

λ”°λΌμ„œ μƒμœ„ νƒ€μž…μΈ Animal 이 ν•˜μœ„ νƒ€μž…μΈ Dog λ˜λŠ” Cat 으둜 subtyping direction 이 μ—­μ „λ˜λŠ” 것은 Type Safe ν•˜μ§€λ§Œ, μ •λ°©ν–₯은 Type Unsafe ν•œ λ°˜λŒ€μ˜ 상황이 λ§Œλ“€μ–΄μ§€λŠ” 것이닀.
λ°”λ‘œ μ €μž₯이 되기 μœ„ν•¨μΈκ°€?, λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μ„ μˆ˜ν–‰ν•˜κΈ° μœ„ν•¨μΈκ°€μ˜ 차이에 따라 μ–΄λ–€ 것이 Type Safe ν•œκ°€λ₯Ό 생각해보면 μ•Œ 수 μžˆλŠ” 것이닀.

λ”°λΌμ„œ Function Parameter Types λŠ” Dog β†’ Animal인데 (Animal) -> void β†’ (Dog) -> voidκ°€ μ„±λ¦½λ˜λ―€λ‘œ Origin Types 의 subtyping direction 의 μ—­λ°©ν–₯을 κ°–λŠ” Contravarianceλ‹€.

Function 의 Parameter Types λŠ” Contravariantν•˜κ³ , Return Types λŠ” Covariantν•΄μ•Ό Type Safe ν•˜λ‹€.


μ’€ 더 λ³΅μž‘ν•œ μΌ€μ΄μŠ€λ₯Ό μƒκ°ν•΄λ³΄μž.

// Case 1
const animalResolverLater: ((f: (animal: Animal) => void) => void) = (f) => f({} as Animal)
const dogResolverLater: ((f: (dog: Dog) => void) => void) = animalResolverLater     // error

λŠ” λΆˆκ°€λŠ₯ν•œλ°

// Case 2
const dogResolverLater: ((f: (dog: Dog) => void) => void) = (f) => f({} as Dog)
const animalResolverLater: ((f: (animal: Animal) => void) => void) = dogResolverLater

λŠ” κ°€λŠ₯ν•˜λ‹€.

얼핏 보면 Case 1 의 μ½”λ“œκ°€ Parameter Types κ°€ Animal μ—μ„œ Dog둜 μ—­μ „λ˜λ‹ˆ λ§žλŠ” 것 κ°™μ•„ 보인닀. ν•˜μ§€λ§Œ μˆœμ„œλŒ€λ‘œ μ°¨κ·Όμ°¨κ·Ό 따져보면 Case 2 의 μ½”λ“œκ°€ κ°€λŠ₯ν•œ μΌ€μ΄μŠ€λΌλŠ” 것을 μ•Œ 수 μžˆλ‹€.

const dog: Dog = {} as Dog
const animal: Animal = dog

Animal 에 Dogλ₯Ό μ €μž₯ν•œλ‹€. Covariant ν•œ 정상 μ½”λ“œλ‹€.

const animalHandler: (_: Animal) => void = (animal) => console.log(animal)
const dogHandler: (_: Dog) => void = (dog) => animalHandler

Dog에 Animal Parameter Types κ°€ μ €μž₯λ˜λ―€λ‘œ 역전이 λœλ‹€. Contravariant ν•œ 정상 μ½”λ“œλ‹€.

const dogResolverLater: ((f: (dog: Dog) => void) => void) = (f) => f({} as Dog)
const animalResolverLater: ((f: (animal: Animal) => void) => void) = dogResolverLater

이제 λ‹€μ‹œ μ—­μ „λ˜μ–΄ (Animal) -> void에 (Dog) -> void Parameter Types κ°€ μ €μž₯λ˜λ―€λ‘œ 역전이 λœλ‹€. Contravariant ν•œ 정상적인 μ½”λ“œλ‹€. λ§Œμ•½, 이 μ½”λ“œκ°€ 잘 이해가 λ˜μ§€ μ•ŠλŠ”λ‹€λ©΄ alias λ₯Ό μ‚¬μš©ν•΄ 가독성을 λ†’μ—¬λ³΄μž.

const dog: Dog = {} as Dog
const animal: Animal = dog

type AnimalHandler = (animal: Animal) => void
type DogHandler = (dog: Dog) => void

const animalHandler: AnimalHandler = (animal) => console.log(animal)
const dogHandler: DogHandler = (dog) => animalHandler

const dogResolverLater: (f: DogHandler) => void = (f) => f({} as Dog)
const animalResolverLater: (f: AnimalHandler) => void = dogResolverLater

즉, Animalκ³Ό Dogλ₯Ό λ³΄λŠ” 것이 μ•„λ‹ˆλΌ (Animal) -> void와 (Dog) -> voidλ₯Ό 봐야 ν•˜λŠ” 것이닀.

5. Invariance

Invariant λ₯Ό λ‹€λ£°λ•Œ μœ μ˜ν•΄μ•Ό ν•  것이 λ°”λ‘œ Generics이닀. μœ„ TypeScript 4.7 μ—μ„œ μ†Œκ°œν•œ in, out modifier 와 ν•¨κ»˜ μ•Œμ•„λ³΄μž.

class Container {
  item: Animal

  constructor(item: Animal) {
    this.item = item
  }
}
const containerStoreDog: Container = new Container({} as Dog)
const containerStoreAnimal: Container = containerStoreDog

기본적으둜 Covariant ν•˜κΈ° λ•Œλ¬Έμ— μœ„ μ½”λ“œλŠ” 정상이닀.


ν•˜μ§€λ§Œ Generics λ₯Ό μ‚¬μš©ν•œ μ½”λ“œμ—μ„œλŠ” μ–΄λ–¨κΉŒ?

class Container<T> {
    item: T

    constructor(item: T) {
        this.item = item
    }
}
const animalContainer: Container<Animal> = new Container<Animal>({} as Animal)
const dogContainer: Container<Dog> = animalContainer  // error
const dogContainer: Container<Dog> = new Container<Dog>({} as Dog)
const animalContainer: Container<Animal> = dogContainer

TypeScript λŠ” 기본적으둜 Genericsλ₯Ό μ‚¬μš©ν•΄ μ»¨ν…Œμ΄λ„ˆλ₯Ό μƒμ„±ν•˜λ”λΌλ„ Covariant λ₯Ό μœ μ§€ν•œλ‹€!


Generics λ₯Ό μ‚¬μš©ν•  경우 Invariantν•˜λ„λ‘ ν•˜κΈ° μœ„ν•΄ in out modifier λ₯Ό μ‚¬μš©ν•΄λ³΄μž.

class Container<in out T> {
  item: T

  constructor(item: T) {
    this.item = item
  }
}
const animalContainer: Container<Animal> = new Container<Animal>({} as Animal)
const dogContainer: Container<Dog> = animalContainer  // error
const dogContainer: Container<Dog> = new Container<Dog>({} as Dog)
const animalContainer: Container<Animal> = dogContainer // error

이제 ContainerλŠ” input κ³Ό output λͺ¨λ‘ Tλ₯Ό μ‚¬μš©ν•˜κ² λ‹€λŠ” κ²ƒμ΄λ―€λ‘œ Invariant ν•˜λ‹€.


3. Variance in Swift πŸ‘©β€πŸ’»

1. Origin Types

class Animal {}
class Dog: Animal {}

let animal: Animal = Dog()

Dog β†’ Animal의 subtyping direction 을 κ°–λŠ”λ‹€.

2. Covariance

let dogArray: [Dog] = []
let animalArray: [Animal] = dogArray

Array μ»¨ν…Œμ΄λ„ˆλ‘œ λž˜ν•‘ν•œ Dog λŠ” μ—¬μ „νžˆ Array μ»¨ν…Œμ΄λ„ˆλ‘œ λž˜ν•‘ν•œ Animal 의 subtype 이닀.

[Dog] β†’ [Animal]

Swift 의 Array λŠ” Origin Types 의 subtyping direction κ³Ό λ™μΌν•œ λ°©ν–₯ κ°–λŠ” Covarianceλ‹€.


Covariance 의 또 λ‹€λ₯Έ μ˜ˆλŠ” Closures Retun Typesλ‹€.

let dogBuilder: () -> Dog = { Dog() }
let animalBuilder: () -> Animal = dogBuilder

Dog β†’ Animalκ³Ό λ™μΌν•œ λ°©ν–₯ () -> Dog β†’ () -> Animalμ΄λ―€λ‘œ ν•¨μˆ˜μ˜ Return Types λŠ” Covarianceλ‹€.

3. Contravariance

그런데 Closures Parameter Typesμ—μ„œλŠ” Origin Types 와 λ‹€λ₯Έ λ°©ν–₯을 κ°–λŠ”λ‹€.

let dogHandler: (Dog) -> Void = { print($0) }
let animalHandler: (Animal) -> Void = dogHandler    // error

Dog β†’ Animalμ΄μ§€λ§Œ (Dog) -> Void β†’ (Animal) -> VoidλŠ” ν—ˆμš©λ˜μ§€ μ•ŠλŠ”λ‹€. 이것은 Type Unsafe ν•˜κΈ° λ•Œλ¬Έμ΄λ‹€.

let animal: Animal = Dog()
let animalArray: [Animal] = [Dog(), Dog(), Dog()]

Covariant ν•œ μœ„ μ½”λ“œλ₯Ό 보자. 이것이 ν—ˆμš©λ˜λŠ” 것은 Animal μ΄λΌλŠ” μ»¨ν…Œμ΄λ„ˆκ°€ 더 크기 λ•Œλ¬Έμ— Dog λ₯Ό μ €μž₯ν•˜λŠ” 것이 κ°€λŠ₯ν•˜κΈ° λ•Œλ¬Έμ΄λ‹€.

let dogBuilder: () -> Dog = { Dog() }
let animalBuilder: () -> Animal = dogBuilder

λ§ˆμ°¬κ°€μ§€λ‘œ, Closure Return TypesλŠ” μ–΄λ”˜κ°€μ— μ €μž₯ν•˜κΈ° μœ„ν•¨μ΄λΌλŠ” 것을 κΈ°μ–΅ν•˜μž. μ• μ΄ˆμ— let animal: Animal = Dog() μ½”λ“œλŠ” Classes 의 initializersλ₯Ό ν˜ΈμΆœν•˜λŠ” μ½”λ“œμ΄κ³ , 이것은 Class Instances λ₯Ό 생성해 λ°˜ν™˜ν•˜λŠ” νŠΉμˆ˜ν•œ ν•¨μˆ˜μ΄λ‹€. 예λ₯Ό λ“€μ–΄ Classes λ₯Ό Singleton으둜 λ§Œλ“€μ–΄ Instances λ₯Ό μƒμ„±ν•˜λŠ” λ‹€λ₯Έ ν•¨μˆ˜λ₯Ό λ§Œλ“€μ–΄ μ‚¬μš©ν•œλ‹€κ³  이 κ·œμΉ™μ΄ κΉ¨μ§„λ‹€λŠ” 것이 말이 μ•ˆ λ˜λŠ” 것이닀. 즉, Closure Return Types λŠ” μ–΄λ”˜κ°€ μ €μž₯되기 μœ„ν•¨μ΄κ³ , λ‹Ήμ—°νžˆ 더 큰 μ»¨ν…Œμ΄λ„ˆλ‘œ λ‚΄λ³΄λ‚΄λŠ” 것이 κ°€λŠ₯ν•΄μ•Ό ν•œλ‹€.

ν•˜μ§€λ§Œ Closure Parameter Types의 κ²½μš°λŠ” λ‹€λ₯΄λ‹€! 이것은 μ–΄λ”˜κ°€μ— μ €μž₯되기 μœ„ν•¨μ΄ μ•„λ‹Œ, Closures 의 Body λ‚΄μ—μ„œ μ–΄λ– ν•œ λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μ„ μˆ˜ν–‰ν•˜κΈ° μœ„ν•¨μ΄λ‹€. 즉, Dog μ—λ§Œ μ‘΄μž¬ν•˜λŠ” 것듀을 ν•„μš”λ‘œ ν•œλ‹€λŠ” 것인데, λ§Œμ•½ Animal 이 ν—ˆμš©λ˜μ–΄ 또 λ‹€λ₯Έ ν•˜μœ„ νƒ€μž…μΈ Cat 이 λ“€μ–΄μ˜¬ 경우, λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μ€ μˆ˜ν–‰ν•  수 μ—†κ²Œ 되고, μ—λŸ¬λ₯Ό λ°œμƒμ‹œν‚¨λ‹€.

let animalHandler: (Animal) -> Void = { print($0) }
let dogHandler: (Dog) -> Void = animalHandler

λ”°λΌμ„œ μƒμœ„ νƒ€μž…μΈ Animal 이 ν•˜μœ„ νƒ€μž…μΈ Dog λ˜λŠ” Cat 으둜 subtyping direction 이 μ—­μ „λ˜λŠ” 것은 Type Safe ν•˜μ§€λ§Œ, μ •λ°©ν–₯은 Type Unsafe ν•œ λ°˜λŒ€μ˜ 상황이 λ§Œλ“€μ–΄μ§€λŠ” 것이닀.
λ°”λ‘œ μ €μž₯이 되기 μœ„ν•¨μΈκ°€?, λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μ„ μˆ˜ν–‰ν•˜κΈ° μœ„ν•¨μΈκ°€μ˜ 차이에 따라 μ–΄λ–€ 것이 Type Safe ν•œκ°€λ₯Ό 생각해보면 μ•Œ 수 μžˆλŠ” 것이닀.

λ”°λΌμ„œ Closure Parameter Types λŠ” Dog β†’ Animal인데 (Animal) -> Void β†’ (Dog) -> Voidκ°€ μ„±λ¦½λ˜λ―€λ‘œ Origin Types 의 subtyping direction 의 μ—­λ°©ν–₯을 κ°–λŠ” Contravarianceλ‹€.

Closure 의 Parameter Types λŠ” Contravariantν•˜κ³ , Return Types λŠ” Covariantν•΄μ•Ό Type Safe ν•˜λ‹€.


μ’€ 더 λ³΅μž‘ν•œ μΌ€μ΄μŠ€λ₯Ό μƒκ°ν•΄λ³΄μž.

// Case 1
let animalResolverLater: ((Animal) -> Void) -> Void = { f in f(Animal()) }
let dogResolverLater: ((Dog) -> Void) -> Void = animalResolverLater     // error

λŠ” λΆˆκ°€λŠ₯ν•œλ°

// Case 2
let dogResolverLater: ((Dog) -> Void) -> Void = { f in f(Dog()) }
let animalResolverLater: ((Animal) -> Void) -> Void = dogResolverLater

λŠ” κ°€λŠ₯ν•˜λ‹€.

얼핏 보면 Case 1 의 μ½”λ“œκ°€ Parameter Types κ°€ Animal μ—μ„œ Dog둜 μ—­μ „λ˜λ‹ˆ λ§žλŠ” 것 κ°™μ•„ 보인닀. ν•˜μ§€λ§Œ μˆœμ„œλŒ€λ‘œ μ°¨κ·Όμ°¨κ·Ό 따져보면 Case 2 의 μ½”λ“œκ°€ κ°€λŠ₯ν•œ μΌ€μ΄μŠ€λΌλŠ” 것을 μ•Œ 수 μžˆλ‹€.

let dog: Dog = Dog()
let animal: Animal = dog

Animal 에 Dogλ₯Ό μ €μž₯ν•œλ‹€. Covariant ν•œ 정상 μ½”λ“œλ‹€.

let animalHandler: (Animal) -> Void = { print($0) }
let dogHandler: (Dog) -> Void = animalHandler

Dog에 Animal Parameter Types κ°€ μ €μž₯λ˜λ―€λ‘œ 역전이 λœλ‹€. Contravariant ν•œ 정상 μ½”λ“œλ‹€.

let dogResolverLater: ((Dog) -> Void) -> Void = { f in f(Dog()) }
let animalResolverLater: ((Animal) -> Void) -> Void = dogResolverLater

이제 λ‹€μ‹œ μ—­μ „λ˜μ–΄ (Animal) -> Void에 (Dog) -> Void Parameter Types κ°€ μ €μž₯λ˜λ―€λ‘œ 역전이 λœλ‹€. Contravariant ν•œ 정상적인 μ½”λ“œλ‹€. λ§Œμ•½, 이 μ½”λ“œκ°€ 잘 이해가 λ˜μ§€ μ•ŠλŠ”λ‹€λ©΄ typealias λ₯Ό μ‚¬μš©ν•΄ 가독성을 λ†’μ—¬λ³΄μž.

let dog: Dog = Dog()
let animal: Animal = dog

typealias AnimalHandler = (Animal) -> Void
typealias DogHandler = (Dog) -> Void

let animalHandler: AnimalHandler = { print($0) }
let dogHandler: DogHandler = animalHandler

let dogResolverLater: (DogHandler) -> Void = { f in f(Dog()) }
let animalResolverLater: (AnimalHandler) -> Void = dogResolverLater

즉, Animalκ³Ό Dogλ₯Ό λ³΄λŠ” 것이 μ•„λ‹ˆλΌ (Animal) -> Void와 (Dog) -> Voidλ₯Ό 봐야 ν•˜λŠ” 것이닀.

4. Invariance

Swift 의 μ‹œμŠ€ν…œμ€ λŒ€λΆ€λΆ„ Invariant ν•˜λ‹€. κ·Έ 쀑 μœ μ˜ν•΄μ•Ό ν•  것이 λ°”λ‘œ Generics이닀.

struct Container {
    var item: Animal
}
let containerStoreDog: Container = Container(item: Dog())
let containerStoreAnimal: Container = containerStoreDog

기본적으둜 Structures 와 Classes λŠ” Covariant ν•˜λ‹€. Structures λŒ€μ‹  Structures λ₯Ό μ‚¬μš©ν•΄λ„ λ‹Ήμ—°νžˆ μœ„ μ½”λ“œλŠ” 정상이닀.


ν•˜μ§€λ§Œ Generics λ₯Ό μ‚¬μš©ν•œ μ½”λ“œμ—μ„œλŠ” μ–΄λ–¨κΉŒ?

struct Container<T> {
    var item: T
}
let animalContainer: Container<Animal> = Container(item: Animal())
let dogContainer: Container<Dog> = animalContainer  // error
let dogContainer: Container<Dog> = Container(item: Dog())
let animalContainer: Container<Animal> = dogContainer   // error

GenericsλŠ” νƒ€μž… 좔둠을 μ‚¬μš©ν•œ 동적 νƒ€μž…μœΌλ‘œ μˆ˜λ§Žμ€ μ½”λ“œμ˜ μ˜€λ²„λ‘œλ”©μ„ ν•˜λ‚˜μ˜ μ½”λ“œλ‘œ 쀄이며 Type Safe ν•œ μ½”λ“œλ₯Ό λ§Œλ“€μ–΄μ€€λ‹€. ν•˜μ§€λ§Œ Generics λŠ” Invariant ν•˜κΈ° λ•Œλ¬Έμ— μ£Όμ˜ν•΄μ•Όν•œλ‹€.




Reference

  1. β€œCovariance and contravariance in generics.” Microsoft.Net. Sep. 15, 2021, Covariance and contravariance in generics.
  2. delmaSong. β€œitem 32. μ œλ„€λ¦­κ³Ό κ°€λ³€μΈμˆ˜(varargs)λ₯Ό ν•¨κ»˜ μ“Έ λ•ŒλŠ” μ‹ μ€‘ν•˜λΌβ€ GitHub. Dec. 28, 2020, item 32. μ œλ„€λ¦­κ³Ό κ°€λ³€μΈμˆ˜(varargs)λ₯Ό ν•¨κ»˜ μ“Έ λ•ŒλŠ” μ‹ μ€‘ν•˜λΌ.
  3. Daniel Rosenwasser. β€œAnnouncing TypeScript 4.7”. Microsoft TypeScript. May. 24, 2022, Announcing TypeScript 4.7 - Optional Variance Annotations for Type Parameters.
  4. β€œCovariance and contravariance (computer science).” Wikipedia. Dec. 12, 2023, Wikipedia - Inheritance in object-oriented languages.
  5. aunnn. β€œCovariance and Contravariance in Swift.” Medium, Feb. 24, 2018, Covariance and Contravariance in Swift.