3.4 분산(Variance)
3.4 분산(Variance)
분산은 다른 곳에서도 관련이 있지만, 특히 타입 매개변수와 함께 자주 나타나며 이 문맥에서 의외일 수 있어요. 분산 오류는 매우 쉽게 발생시킬 수 있습니다:
class Base {
public function new() {}
}
class Child extends Base {}
class Main {
public static function main() {
var children = [new Child()];
// Array<Base> should be Array<Child>
// Type parameters are invariant
// Child should be Base
var bases:Array<Base> = children;
}
}
Child가 Base에 할당될 수 있는데도 Array<Child>는 Array<Base>에 할당될 수 없는 것 같아요. 그 이유는 다소 예상 밖일 수 있어요. 배열엔 예를 들어 push() 메서드를 통해 쓸 수 있기 때문에 할당이 허용되지 않는 겁니다. 분산 오류를 무시하면 문제가 생기기 쉽습니다:
class Base {
public function new() {}
}
class Child extends Base {}
class OtherChild extends Base {}
class Main {
public static function main() {
var children = [new Child()];
// subvert type checker
var bases:Array<Base> = cast children;
bases.push(new OtherChild());
for (child in children) {
trace(child);
}
}
}
여기서 우리는 캐스팅(cast)으로 타입 검사기를 무너뜨려, 주석 처리된 줄 뒤의 할당을 허용해요. 그렇게 원래 배열을 가리키는 Array<Base> 타입의 참조 bases를 갖게 됩니다. 이는 Base와 호환되는 다른 타입, 여기서는 OtherChild를 그 배열에 push하는 것을 허용해요. 하지만 우리의 원래 참조 children은 여전히 Array<Child> 타입이라서, 요소 중 하나에서 OtherChild 인스턴스를 만나면 문제가 생깁니다.
이런 이유로 Haxe는 타입 매개변수를 불변(invariant)으로 간주해요. Array<Child>와 Array<Base>는 Child와 Base의 관계와 무관하게 서로 호환되지 않습니다. 불변 타입 매개변수의 규칙에서 두 예외가 있어요:
Dynamic과 관련된 유니피케이션은 항상 성공하고,Dynamic과 유니파이하는 것은 성공해야 해요. 엄밀히 말해Dynamic은 불변 타입 매개변수를 무시할 수 있어요.- 불변 타입 매개변수의 모노모프(monomorphs) 유니피케이션은 항상 성공해요. (간단한 예:
var a = new Array(); var b:Array<Int> = a;)
출처: Variance
본문
분산의 개념
타입 매개변수의 분산은 제네릭 타입의 상하 관계가 매개변수 타입의 상하 관계를 따르는지 정의해요. Haxe는 타입 매개변수를 기본적으로 불변(invariant)으로 취급합니다.
불변의 이유
배열은 push()처럼 쓰기 가능한 연산이 있어서, Array<Child>를 Array<Base>에 할당하면 타입 안전성 문제가 생길 수 있어요. 그 때문에 Child를 Base에 할당할 수 있어도 배열 타입끼리는 호환되지 않아요.
예외
두 가지 예외가 있어요: Dynamic과의 유니피케이션은 항상 성공하고, 모노모프와의 유니피케이션도 항상 성공합니다.