함수 가변성
몇 가지 예를 살펴보겠습니다.
함수 A가 함수 B와 같거나 적은 수의 매개변수를 가지며 다음을 만족하면, A는 B의 서브 타입입니다.
- A의 this 타입을 따로 지정하지 않으면 'A의 this 타입 >: B의 this 타입'입니다.
- 'A의 각 매개변수 >: B의 대응 매개변수'입니다.
- 'A의 반환 타입 <: B의 반환 타입'입니다.
위의 문장들을 여러 번 읽으면서 각각의 규칙이 무엇을 의미하는지 정확하게 이해해야 합니다.
함수 A가 함수 B의 서브 타입이라면 A의 this 타입과 매개 변수는 B에 대응하는 this 타입과 매개변수에 >: 관계를 만족해야 하며 반환 타입은 <: 관계가 아니어야 합니다.
왜 반환 타입만 조건이 반대이며, 객체, 배열, 유니온 등과 달리 함수에서는 this, 매개변수, 반환 타입을 포함한 모든 컴포넌트가 <: 관계를 만족하지 않는 이유는 무엇일까요?
직접 추론해 보면서 위 질문의 답을 찾아보겠습니다.
먼저 세 개의 타입을 정의합니다. (이번 예시에선 편의상 class를 사용했지만 A <: B <: C의 관계를 만족하는 어떤 타입이든 사용할 수 있습니다.)
class Flower {}
class Rose extends Flower{
red(){}
}
class Thron extends Rose{
sharp(){}
}
여기에서 Thron은 Rose의 서브 타입이고, Rose는 Flower의 서브 타입입니다.
즉, Thron <: Rose <: Flower 조건이 성립합니다.
이제 Rose가 빨간색일 수 있도록 함수를 정의합니다.
class Flower {}
class Rose extends Flower{
red(){}
}
class Thron extends Rose{
sharp(){}
}
function red(rose: Rose): Rose {
rose.red()
return rose
}
지금까진 아무 문제가 없습니다.
TypeScript는 red에 어떤 타입을 전달하도록 허용할까요?
class Flower {}
class Rose extends Flower{
red(){}
}
class Thron extends Rose{
sharp(){}
}
function red(rose: Rose): Rose {
rose.red()
return rose
}
// 'Flower' 형식의 인수는 'Rose' 형식의 매개 변수에 할당될 수 없습니다.
// 'red' 속성이 'Flower' 형식에 없지만 'Rose' 형식에서 필수입니다.ts(2345)
red(new Flower)
red(new Rose)
red(new Thron)
Rose 인스턴스(red의 매개변수 rose의 타입이 Rose이므로) 또는 Thron 인스턴스(Thron은 Rose의 서브 타입이므로)는 red에 전달할 수 있습니다.
예상대로 서브 타입은 성공적으로 전달할 수 있었습니다.
매개변수가 함수 타입인 새로운 새 함수를 만들어보겠습니다.
class Flower {}
class Rose extends Flower{
red(){}
}
class Thron extends Rose{
sharp(){}
}
function red(rose: Rose): Rose {
rose.red()
return rose
}
function clone(f: (r: Rose) => Rose): void {
// 블라블라
}
clone 함수는 Rose를 인수로 받아 Rose를 반환하는 함수 f를 받습니다.
f엔 어떤 함수를 안전하게 전달할 수 있을까요?
함수 정의에 따르면 Rose를 인수로 받아 Rose를 반환하는 함수를 전달할 수 있습니다.
class Flower {}
class Rose extends Flower{
red(){}
}
class Thron extends Rose{
sharp(){}
}
function red(rose: Rose): Rose {
rose.red()
return rose
}
function clone(f: (r: Rose) => Rose): void {
// 블라블라
}
function roseToRose(r: Rose): Rose {
// 블라블라
}
clone(roseToRose) // OK
Rose를 인수로 받아 Thron이나 Flower를 반환하는 함수도 전달할 수 있을까요?
class Flower {}
class Rose extends Flower{
red(){}
}
class Thron extends Rose{
sharp(){}
}
function red(rose: Rose): Rose {
rose.red()
return rose
}
function clone(f: (r: Rose) => Rose): void {
// 블라블라
}
function roseToRose(r: Rose): Rose {
// 블라블라
}
clone(roseToRose) // OK
function roseToThron(t: Rose): Thron {
// 블라블라
}
clone(roseToThron) // OK
function roseToFlower(t: Rose): Flower{
// 블라블라
}
// '(t: Rose) => Flower' 형식의 인수는 '(r: Rose) => Rose' 형식의 매개 변수에 할당될 수 없습니다.
// 'red' 속성이 'Flower' 형식에 없지만 'Rose' 형식에서 필수입니다.ts(2345)
// index.ts(4, 3): 여기서는 'red'이(가) 선언됩니다.
clone(roseToFlower)
roseToThron은 정상 동작하지만 roseToFlower에서 에러가 발생합니다.
이유가 무엇일까요?
clone의 구현이 다음과 같다고 해보겠습니다.
function clone(f: (r: Rose) => Rose): void {
let parent = new Rose
let smallthron = f(parent)
smallthron.red()
}
clone에 Flower를 반환하는 함수 f를 전달한다면, f의 반환값에 .red를 호출할 수 없습니다.
이런 이유로 TypeScript는 전달한 함수가 적어도 Rose인지 컴파일 타임에 확인합니다.
함수의 반환 타입은 공변, 즉 함수가 다른 함수의 서브 타입이라면 '서브 타입 함수의 반환 타입 <: 다른 함수의 반환 타입'을 만족해야 합니다.
매개변수 타입의 관계는 어떨까요?
function flowerToRose(f: Flower): Rose {
// 블라 블라
}
clone(flowerToRose) // OK
function thronToRose(t: Thron): Rose{
// 블라 블라
}
// '(t: Thron) => Rose' 형식의 인수는 '(r: Rose) => Rose' 형식의 매개 변수에 할당될 수 없습니다.
// 't' 및 'r' 매개 변수의 형식이 호환되지 않습니다.
// 'sharp' 속성이 'Rose' 형식에 없지만 'Thron' 형식에서 필수입니다.ts(2345)
// index.ts(8, 3): 여기서는 'sharp'이(가) 선언됩니다.
clone(thronToRose)
함수를 다른 함수에 할당하려면 'this를 포함한 매개변수 타입 >: 할당하려는 함수에 대응 매개변수 타입' 조건을 만족해야 합니다.
thronToRose 함수를 구현했고 clone에 전달하려 한다고 가정하면서 그 이유를 찾아보겠습니다.
function thronToRose(t: Thron): Rose{
t.sharp()
return new Rose
}
clone에 thronToRose를 전달하면 .sharp는 Thron에만 정의되어 있고 Rose에는 정의되어 있지 않으므로 예외가 발생합니다.
즉 함수의 매개변수, this 타입은 반변입니다.
한 함수가 다른 함수의 서브 타입이라면 '서브 타입 함수의 매개변수와 this 타입 >: 다른 함수의 대응하는 매개변수'라는 조건을 만족해야 합니다.
정말 다행히도... 이런 규칙들을 외울 필요는 없습니다. 코드 편집기로 코딩할 때 규칙을 어기고 잘못된 타입의 함수를 전달하면 빨간 밑줄이 그어질 것이기 때문입니다:)
'👶 TypeScript' 카테고리의 다른 글
타입 넓히기 - const 타입 (0) | 2023.01.18 |
---|---|
할당성(assignability) (0) | 2023.01.18 |
형태와 배열 가변성 (0) | 2023.01.18 |
가변성 (0) | 2023.01.17 |
서브 타입과 슈퍼 타입 (0) | 2023.01.17 |