타입 시스템에서는 Type variance(타입가변성)이라는 개념이 있습니다. 타입과 서브타입의 관계를 서술한 것들인데 언어마다 각기 다른방식으로 동작합니다. 함수형언어의 경우는 타입가변성을 조절 할 수도 있습니다.

Type variance에는 무엇이 있을까

  • 공변 (covariane)
  • 반공변 (contravariance)
  • 무공변(invariance)
  • 이분?(Bivariance)

이렇게 세가지 타입이 있습니다.

class Animal {}
class People extends Animal {}
class Hou extends People {}

공변(Covariance)

A가 B의 서브타입일 경우 T<A>T<B>의 서브타입일 경우를 말합니다
리스코프 치환 원칙과 관련이 있습니다.

function method(People p) {}
method(animal) => error
method(people) => ok
method(hou) => ok

반공변(contravariance)

A가 B의 서브타입일 경우 T<B>T<A>의 서브타입일 경우를 말합니다

function method(People p) {}
method(animal) => ok
method(people) => ok
method(hou) => error

무공변(invariance)

다른 타입을 허용하지 않음

function method(People p) {}
method(animal) => error
method(people) => ok
method(hou) => error

Bivariance

아무타입이나 허용함

function method(People p) {}
method(animal) => ok
method(people) => ok
method(hou) => ok

타입스크립트에서의 타입시스템

  • 기본적으로는 공변입니다.
  • 메소드 인수에서는 Bivariance적인 특징을 갖는 버그가 있다가 1.6 부터 strictFunctionTypes에서 해결되었습니다.
  • 메소드 인수에서는 반공변 입니다.
class Animal {
    name: string
    constructor() {
        this.name = 'animal'
    }
}

class People extends Animal {
    constructor() {
        super()
        this.name = 'people'
    }
    coding() {}
}

class Hou extends People {
    constructor() {
        super()
        this.name = 'hou'
    }
    queck(){}
}

let method = (a: People): People => a

let animal = new Animal()
let people = new People()
let hou = new Hou()

method(animal)
method(people)
method(hou)

//A<T>
let animals: Animal[] = new Array(5);
let peoples: People[] = new Array(5);
let hous: Hou[] = new Array(5);
animals[0] = hou
animals[1] = people
peoples[0] = animal //error
peoples[1] = hou

let coding = (a: People): People => { a.coding(); return a }
coding(animal[0])
coding(animal[0])
coding(animal) //error
coding(peoples[0])
coding(peoples[1])

//메서드 인수의 반공변성 
let exMethod1: (x: Animal) => void
let exMethod2: (x: People) => void
let exMethod3: (x: Hou) => void

exMethod1 = exMethod2 // Error with --strictFunctionTypes
exMethod2 = exMethod1 // ok
exMethod2 = exMethod3 // Error with --strictFunctionTypes
exMethod3 = exMethod2 // ok


animal = people //ok
people = animal // error
people = hou // ok
hou = people // error 


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

declare let animalComparer2: Comparer2<Animal>;
declare let dogComparer2: Comparer2<People>;

animalComparer2 = dogComparer2;
dogComparer2 = animalComparer2;

//메서드 인수와 리턴타입의 공변과 반공변성 

declare let AnimalToAnimal: (a: Animal) => Animal;
declare let PeopleToAnimal: (a: People) => Animal;
declare let AnimalToPeople: (a: Animal) => People;
declare let PeopleToPeople: (a: People) => People;

AnimalToAnimal = PeopleToAnimal //err with --strictFunctionTypes
AnimalToAnimal = AnimalToPeople
AnimalToAnimal = PeopleToPeople //err with --strictFunctionTypes

PeopleToPeople = AnimalToAnimal //err
PeopleToPeople = PeopleToAnimal //err
PeopleToPeople = AnimalToPeople
//------------------------------
//하위형에서 메서드 인수의 반공변성
//하위형에서 반환형의 공변성

let f1: (x: Animal) => void;
let f2: (x: Dog) => void;
let f3: (x: Cat) => void;
f1 = f2;  // Error with --strictFunctionTypes
f2 = f1;  // Ok
f2 = f3;  // Error

let animalArr: Animal[] = [animal];
let dogArr: Dog[] = [dog];
//----------------------------------

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

declare let animalComparer: Comparer<Animal>;
declare let dogComparer: Comparer<Dog>;

animalComparer = dogComparer;
dogComparer = animalComparer;


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

declare let animalComparer2: Comparer2<Animal>;
declare let dogComparer2: Comparer2<Dog>;

animalComparer2 = dogComparer2;
dogComparer2 = animalComparer2;

declare let aa: (a: Animal) => Animal;
declare let da: (a: Dog) => Animal;
declare let ad: (a: Animal) => Dog;
declare let dd: (a: Dog) => Dog;

aa = da
aa = ad
aa = dd

dd = aa
dd = da
dd = ad


/**
 * Obviously Bad : Contravariance
 * Animal <= Cat
 * Animal[] >= Cat[]
 */
dogArr = animalArr; // Okay if contravariant
dogArr[0].wal(); // Allowed but BANG 🔫 at runtime


/** Type Hierarchy */
interface Point2D { x: number; y: number; }
interface Point3D { x: number; y: number; z: number; }

/** Two sample functions */
let iMakePoint2D = (): Point2D => ({ x: 0, y: 0 });
let iMakePoint3D = (): Point3D => ({ x: 0, y: 0, z: 0 });

/** Assignment */
iMakePoint2D = iMakePoint3D; // Okay
iMakePoint3D = iMakePoint2D; // ERROR: Point2D is not assignable to Point3D

var f: (x: { x: number; }) => void;
var g: (x: { x: number; y: number; }) => void;

g = f; // fair
f = g; // no error, but it should be

let ex1 = (e: Animal): Animal => e;
let ex2 = (e: Dog): Animal => new Animal();
let ex3 = (e: Animal): Dog => new Dog();
let ex4 = (e: Dog): Dog => e;
ex1(animal)
ex2(ex1(animal))
ex3(ex1(animal))
ex1(ex2(dog))
ex4(ex3(dog))
ex1(ex3(dog))