[TS] TypeScript 공변성, 반공변성

1
post-thumbnail

타입스크립트에서 함수의 타입을 정의하고, 함수를 생성할 때 타입을 할당할 수 없다는 에러를 접하는 경우가 있습니다. 함수의 파라미터는 넓은 타입을 좁은타입에 대입할 수 있고, 반대로 함수의 리턴 타입은 좁은 타입을 넓은 타입에 대입할 수 있습니다.

이러한 현상을 타입스크립트의 공변성(Covariance), 반공변성(Contravariance)의 개념으로 이해할 수 있는데요. 공변성, 반공변성 단어부터 어렵고 헷갈린 개념이기에 먼저 각 함수의 예시를 통해 간단히 살펴보도록 하겠습니다.

함수의 파라미터 타입

파라미터 타입이 넓은타입을 좁은타입에 넣기

let wide = (param : string | number ):string => {
	return param.toString();
}

type narrow = (param : string ) => string;

let wideToNarrow: narrow = wide;

widenarrow 는 함수의 리턴값이 string으로 동일합니다. wide의 파라미터에는 string 타입과 number 타입이 들어올 수 있고, narrow 의 파라미터로는 string 만 들어올 수 있습니다. 즉, widenarrow 보다 넓은 타입입니다.

위 예제에서는 넓은 타입의 파라미터를 가진 함수 wide를 좁은 타입의 파라미터를 가진 narrow 타입에 대입했고, 이때 타입 에러는 발생하지 않습니다.

하지만 반대로 좁은 타입의 파라미터를 가진 함수를 넓은 타입의 파라미터를 가진 함수에 대입하면 어떻게 될까요?

파라미터 타입이 좁은타입을 넓은타입에 넣기 (에러)

let narrow = (param : string ):string => {
	return param.toString();
}

type wide = (param : string  | number) => string;

let narrowToWide: wide = narrow;

좁은 타입의 파라미터를 가진 narrow 함수를 넓은 타입의 파라미터를 가진 wide에 할당하게 되면 numberstring에 대입 할 수 없다는 타입 에러가 발생합니다.

Type 'number' is not assignable to type 'string'.

함수의 리턴타입

이번엔 리턴 타입의 타입 범위가 다른 경우를 살펴보겠습니다.

리턴 타입이 좁은 타입을 넓은 타입에 넣기

let narrow = (param : string ):string => {
	return param.toString();
}

type wide = (param : string ) => string | number;

let narrowToWide: wide = narrow;

widenarrow 는 함수의 파라미터 타입이 string으로 동일합니다. widestring 타입과 number 타입을 리턴하고 narrowstring 만 리턴합니다. 즉, widenarrow 보다 넓은 타입을 리턴합니다.

위 예제에서는 좁은 타입을 리턴하는 narrow를 넓은 타입을 리턴하는 wide에 대입했고, 이때 타입 에러는 발생하지 않습니다.

리턴 타입이 넓은타입을 좁은타입에 넣기 (에러)


let wide = (param : string ):string | number => {
	return param.toString();
}

type narrow = (param : string ) => string;

let wideToNarrow: narrow = wide;

하지만 반대로 리턴타입이 좁은 타입에 리턴 타입이 넓은 타입을 대입 하게 되면 numberstring에 할당 할 수 없다는 타입 에러가 발생합니다.

Type 'number' is not assignable to type 'string'.

정리를 해보자면, 함수의 파라미터는 좁은 타입에 넓은 타입이 들어올 수 있고 함수의 리턴타입은 넓은 타입에 좁은 타입이 들어올 수 있습니다. 좁은 타입에 넓은 타입을 넣을 수 있는 것을 공변성(Covariance), 넓은 타입에 좁은 타입을 넣을 수 있는 성질을 반공변성(Contravariance)이라고 합니다.

공변성(Covariance)과 반공변성(Contravariance)

타입스크립트는 기본적으로 공변적으로 동작하나, 앞서 본 것처럼 타입스크립트의 파라미터는 반공변적 으로 동작합니다.

사실 타입스크립트의 파라미터가 반공변적으로 동작하는 건 TS conifg의 --strictFunctionTypes 옵션이 적용된 strict 모드 기준 입니다. TS conifg의 --strictFunctionTypes 옵션을 on 해야 파라미터의 반공변적 인 성질을 타입스크립트의 규칙으로 삼을 수 있습니다.

만약, 파라미터가 반공변적으로 동작하지 않는다면, stringnumber 를 파라미터로 받아 모두 처리해줘야 하는 함수에 string 타입만 처리할 수 있는 함수를 할당해도 타입 에러가 발생하지 않게 되고, 결국 타입 안정성을 보장하지 못하게 됩니다.

--strictFunctionTypes가 off인 상태, 즉 strict 모드가 아니라면 함수 파라미터는 공변성반공변성 을 모두 가지게 됩니다. 좁은 타입을 넓을 타입에 할당할 때나 넓은 타입을 좁은 타입에 할당할 때 어떤 경우에도 타입 에러가 발생하지 않게 되는 것이죠. 이렇게 공변적인 성질과 반공변적인 성질을 모두 가진 것을 이변성(Bivariance) 이라고 합니다.

마치며

공변성과 반공변성 그리고 이변성의 개념을 공부하면서 변성, 리스코프 치환 원칙 등 추가적으로 이해가 필요한 개념이 많았는데요. 이 부분은 아직 글에 녹여낼만큼 완벽하게 이해하지 못한 관계로, 이번 글은 간단한 함수 예제와 이 예제를 통한 공변성, 반공변성 설명으로 마무리 짓겠습니다. (to be continued..⭐️)

0개의 댓글