# [ 살펴보기 ] Typescript - 구조적 타이핑, 공명성과 반공명성

프로그래밍 언어에서 사용되는 두 가지 주요 타입 시스템이 있다. 하나는 구조적 타이핑이고 다른 하나는 명목적 타이핑이다.

구조적 타이핑은 타입의 호환성을 그 타입의 구조나 속성을 기반으로 평가하고 명목적 타이핑은 타입의 호환성을 타입의 이름과 구조 모두에 기반하여 평가한다. 그리고 Typesciprt는 구조적 타이핑을 기반으로 타입의 호환성을 평가한다.

다음 예를 살펴보자.

```javascript

interface ICar {
  drive(): void;
}

interface ITruck {
  drive(): void;
}

class Car implements ICar {
  drive() {
      console.log("Car");
  }
}

class Truck implements ITruck {
  drive() {
      console.log("Truck");
  }
}

function driveCar(car: ICar) {
  car.drive();
}

const car = new Car();
const truck = new Truck();

driveCar(car); 
driveCar(truck);
```

위에서 Car class는 ICar 인터페이스를 implement하고 Truck class는 ITruck 인터페이스를 implement하고 있다. 그리고 각 Class로 생성한 instance를 ICar 인터페이스를 parameter로 받는 함수에 전달하면 타입 오류 없이 호환된다. ICar Interface와 ITruck Interface의 구조가 동일하기 때문이다.

만일 위 코드를 Java와 같이 명목적 타이핑을 사용하는 언어에서 실행해보면 어떻게 될까?

```javascript

interface ICar {
    void drive();
}

interface ITruck {
    void drive();
}

class Car implements ICar {
    public void drive() {
        System.out.println("Car is driving");
    }
}

class Truck implements ITruck {
    public void drive() {
        System.out.println("Truck is driving");
    }
}

public class Main {
    public static void main(String[] args) {
        ICar car = new Car();
        ICar truck = new Truck(); // type 오류가 발생한다
        System.out.println("Hello world!");
    }
}
```

처음 typesciprt의 예제를 Java언어로 구현하여 실행해 보면 위와 같이 오류가 발생한다. ICar Interface와 ITruck Interface는 서로 구조는 같지만 다른 타입으로 평가되기에 호환이 되지 않는다.

Typesciprt 구조적 타이핑의 또 다른 예를 살펴보자.

```javascript
interface Person {
  name: string;
  age: number;
}

interface Korean {
  name: string;
  age: number;
  address: string;
}

const getPersonInfo = (person: Person) => {
  return { name: person.name, age: person.age };
};

const koreanInfo: Korean = {
  name: "test name",
  age: 20,
  address: "test address",
};

getPersonInfo(koreanInfo);
```

위의 예제에서 우리는 Korean Interface를 가진 객체를 Person Interface를 가진 데이터를 param으로 받는 getPersonInfo 함수에 전달하였다.

우리가 전달한 koreanInfo라는 객체는 Person Interface에 선언되어 있지 않은 address라는 추가 property가 존재하는데도 Typesciprt는 타입 오류가 발생 시키지 않는다. Person Interface가 요구하는 name과 age 속성과 타입을 koreaInfo 객체가 만족하기 때문이다.

이러한 유연성이 강점이 될 수도 있지만 단점 역시 될 수 있다. 다음 같은 경우를 생각해보자.

```javascript
interface Person {
  name: string;
  age: number;
}

interface Korean {
  name: string;
  age: number;
  address: string;
}

const getPersonInfo = (person: Person) => {
  return { name: person.name, age: person.age };
};

const getKoreanInfo = (korean: Korean) => {
  const personInfo = getPersonInfo(korean);
  return personInfo;
};

const koreanInfo: Korean = {
  name: "test name",
  age: 20,
  address: "test address",
};

getKoreanInfo(koreanInfo);
```

getKoreanInfo라는 함수를 추가했다. 그리고 getKoreanInfo라는 함수는 전달 받은 param을 getPersonInfo에 곧 바로 전달해 return된 값을 사용한다. 물론 실제로 코드를 짤 때 위와 같이 짜지는 않겠지만 만약 우리가 getKoreanInfo라는 함수를 통해 argument로 전달한 객체를 그대로 return 받는 의도로 사용한다면 문제가 발생한다.

왜냐하면 getPersonInfo는 name과 age 속성만 있는 Interface를 가진 param을 전달 받아 name와 age 정보만 return하기 때문이다. 하지만 위와 같은 상황에서도 타입 오류가 발생하진 않는다.

getKoreanInfo 함수에서 받는 param의 Interface가 getPersonInfo 함수가 받는 param의 Interface와 호환이 되기 때문이다. ( Korean Interface가 Person Interface에서 요구 하는 속성과 타입을 모두 가지고 있기 때문이다 )

그렇기에 구조적 타이핑의 이러한 속성을 잘 이해하고 사용하는 것이 좋다.

## 공변성 ( Covariance )

공변성을 설명하기전 subtype과 subtype의 base가 되는 basetype의 타입 호환을 우선 살펴보자

```javascript
class Person {
  name: string;
  constructor(username: string) {
    this.name = username;
  }
}

class Korean extends Person {
  city: string;
  constructor(name: string, city: string) {
    super(name);
    this.city = city;
  }
}
```

위의 예제에서 Korean class는 Person class를 extends하고 있으므로 Korean class는 Person class의 subtype이라고 할 수 있다. 그리고 Person은 Korean class의 기반이 되는 basetype이다.

아래의 예제를 살펴보자.

```javascript
const personTest: Person = new Person("person 1");
const koreanTest: Person = new Korean("korean 1", "busan");
```

위의 예제를 살펴보면 Person class를 type으로 사용하는 koreanTest 상수에 Person class의 subtype인 Korean class의 instance를 할당해도 타입 오류가 발생하지 않는다. 즉, subtype은 basetype의 자리에 대신 사용될 수 있다.

위와 같은 특징을 기준으로 A가 B의 subtype일 때 Promise&lt;A&gt;도 Promise&lt;B&gt;의 subtype이고 Record&lt;string, A&gt; 역시 Record&lt;string, B&gt;의 subtype이 되는 성질을 공변성이라고 한다 ( 만약 A가 B의 서브타입이면 `T<A>`는 `T<B>`의 서브타입이다 )

또 다른 예를 살펴보자

```javascript
const personTest: Promise<Person> = new Promise((resolve) => {
  resolve(new Person("person 1"));
});

const koreanTest: Promise<Person> = new Promise((resolve) => {
  resolve(new Korean("korean 1", "busan"));
});
```

이번에는 personTest, koreanTest 모두 Promise 타입으로 정의되었다. 위의 예제에서 볼 수 있듯이 Person class의 subtype인 Korean class를 여전히 Person class를 대신해 사용할 수 있다.

## 반공변성 ( Contravariance )

반공변성이란 공변성에서 살펴봤던 subtype과 basetype의 관계가 역전되는 것을 말한다.

즉, A가 subtype B가 basetype이라면 타입 호환 관계가 반대가 되며 Promise&lt;B&gt;가 Promise&lt;A&gt;의 subtype으로, Record&lt;string, B&gt;가 Record&lt;string, A&gt;의 subtype으로 취급되는 성질이다. ( 만약 A가 B의 서브타입이면 `T<B>`가 `T<A>`의 서브타입 처럼 취급된다 )

tsconfig.json의 옵션 중 strictFunctionTypes을 true로 설정했다면 함수의 parameter는 반공변성을 띈다.

다음 예제를 살펴보자 ( strictFunctionTypes:true인 경우 )

```javascript
let getPersonInfo = (person: Person) => {};
let getKoreanInfo = (person: Korean) => {};

getKoreanInfo = getPersonInfo; 
getPersonInfo = getKoreanInfo; // type 오류
```

공변성에서 살펴봤던 예제에서 subtype이 basetype 자리에 대신 올 수 있었다. 하지만 반공변성 성질을 띄고 있는 상황에선 그 반대가 된다.

예제에서 볼 수 있듯이 subtype인 Korean class를 param으로 받는 getKorean 함수를 basetype인 Person class를 param으로 받는 함수에 할당하지 못하지만 그 반대의 경우는 가능하다. 함수의 parameter는 반공변성을 띄기 때문이다.

만약 tsconfig.json에서 strictFunctionTypes이 false로 설정되어 있다면 함수는 공변성과 반공변성의 성질을 모두 가진 이변성(Bivariance)을 띄게 된다. 그렇기에 아래 코드에 아무런 타입 에러가 발생하지 않는다.

```javascript
let getPersonInfo = (person: Person) => {};
let getKoreanInfo = (person: Korean) => {};

getKoreanInfo = getPersonInfo; 
getPersonInfo = getKoreanInfo;
```

함수의 return 타입은 strictFunctionTypes option과 무관하게 공명성을 띈다. 즉, subtype이 basetype 대신 사용될 수 있지만 그 반대는 안된다.

```javascript
let getPersonInfo = (): Person => {
  return new Person("test 1");
};
let getKoreanInfo = (): Korean => {
  return new Korean("test2", "busan");
};

getKoreanInfo = getPersonInfo; // 타입 오류
getPersonInfo= getKoreanInfo;
```
