흔히 공변성, 반변성 이라고 번역되는 covariant
, contravaiant
에 대해 알아보겠습니다.
variant
의 사전적 뜻은 변종, 이형(異形) 입니다.
~와 같이
라는 뜻의 co
와 붙은 covariant
는 같이 변한다(공변)는 뜻입니다.
~와 반대로
라는 뜻의 contra
와 붙은 contravariant
는 반대로 변한다(반변)는 뜻이겠죠.
스칼라 타입 다이어그램과 같이 보면
모든 타입의 SuperType(조상)인 Any가 있고, 모든 타입의 SubType(자손)인 Nothing 이 있습니다.
SuperType 으로 갈수록 -
, SubType으로 갈수록 +
라고 표현하겠습니다.
객체를 캐스팅할 때 -
방향으로 캐스팅하면 Up Casting 이라고 하고, +
방향으로 캐스팅하면 Down Casting 이라고 하죠.
abstract class Food
case class Egg() extends Food
case class BoiledEgg() extends Egg()
val food: Food = Egg() //upcasting
val boiledEgg: BoiledEgg = Egg() // downcasting error!
음식, 계란, 삶은계란을 예로 들면
계란을 음식에 할당(업캐스팅)할 수 있지만
삶은계란은 (날)계란에 할당(다운캐스팅)할 수 없는게 일반적입니다.
스칼라에서 Covariant 는 [+T]
라는 제네릭으로 표현됩니다.
covariant
는 업캐스팅과 비슷해서 이해하기가 쉽습니다.
흔히 보는 List[+T]
도 covariant
이기 때문에 직관적이죠.
객체와 제네릭이 같은 방향으로 변하면
즉, T
를 업캐스팅해서 T1
이 되었을 때 List[T]
가 List[T1]
이 된다면 covariant
한 것입니다.
코드로 예시를 들어보면 아래와 같습니다.
class Box[+T]
val upcast1: Box[Food] = new Box[Egg]
val upcast2: Box[Egg] = new Box[BoiledEgg]
val upcast3: Box[Food] = new Box[BoiledEgg]
val downcast1: Box[Egg] = new Box[Food] //error!
val downcast2: Box[BoiledEgg] = new Box[Egg] //error!
val downcast3: Box[BoiledEgg] = new Box[Food] //error!
음식을 담는 상자는 계란을 담을 수 있지만,
삶은계란 전용 상자는 모든 음식을 담을 수 없는 것과 마찬가지죠.
스칼라에서 Contravariant 는 [-T]
라는 제네릭으로 표현됩니다.
contravariant
는 다운캐스팅과 유사합니다.
T
를 업캐스팅해서 T1
이 되었을 때 List[T1]
가 List[T]
이 된다면 contravariant
한 것입니다.
하지만 이는 직관적으로 이해하기가 어렵습니다.
코드 예시를 보겠습니다.
val upcast1: Eater[Food] = new Eater[Egg] //error!
val upcast2: Eater[Egg] = new Eater[BoiledEgg] //error!
val upcast3: Eater[Food] = new Eater[BoiledEgg] //error!
val downcast1: Eater[Egg] = new Eater[Food]
val downcast2: Eater[BoiledEgg] = new Eater[Egg]
val downcast3: Eater[BoiledEgg] = new Eater[Food]
모든음식을 먹을 수 있는 사람은 삶은계란을 먹을(consume) 수 있지만,
삶은계란만 먹을 수 있는 사람이 모든 음식을 먹을 수 있는 건 아니죠.
이처럼 contravariant
는 consume 의 측면에서 생각하면 쉽습니다.
개념적으로 접근하자면 클래스가 특정 타입의 출력을 생성하고 그 타입의 인스턴스를 변형하거나 소비(consume) 한다면 Contravariant를 선택할 수 있다. 이는 주로 매개변수 타입에서 일반적이다.
그렇지 않고 특정 타입의 인스턴스를 소비하지 않는다면 Covariant 를 선택할 수 있다. 이는 주로 반환 타입에서 일반적이다.