devcken.io

Thoughts, stories and ideas.

Scala Variances: Covariant, Invariant and Contravariant

원문: Scala Variances: Covariant, Invariant and Contravariant

이 포스트에서, 스칼라의 변성과 유스케이스에 대해서 알아보겠습니다.

변성이란?

변성은 파라메터화된 타입의 상속 관계를 정의합니다. 변성은 서브 타이핑에 관한 모든 것입니다.

"파라메터화된 타입이란 무엇인가"를 이해하기 위해 다음 이미지를 살펴보시기 바랍니다.

여기서 T는 "타입 파라메터"로 알려져 있으며 List[T]는 제네릭(Generic)이라고 합니다.

List[T]의 경우, 만약 List[Int], List[AnyVal] 등을 사용한다면, 이 List[Int]List[AnyVal]를 "파라메터화된 타입"이라고 합니다.

변성은 이런 파라메터화된 타입 간의 상속 관계를 정의합니다.

스칼라 변성의 이점

스칼라 변성의 주요 이점은:

  • 변성은 스칼라 컬렉션을 좀 더 타입에 안전하도록 만듭니다
  • 변성은 좀 더 유연한 개발을 가능하게 합니다
  • 스칼라 변성은 신뢰성 있는 애플리케이션을 개발하기 위한 기술을 제공합니다

스칼라의 변성 타입

스칼라는 다음 세 가지 종류의 변성을 지원합니다.

  • 공변성(Covariant)
  • 무공변성(Invariant)
  • 반공변성(Contravariant)

다음 섹션에서 이 세 가지 변성에 대해 자세히 알아보고자 합니다.

스칼라의 공변성(Covariant)

ST의 서브 타입이면, List[S]List[T]의 서브 타입이다.

두 파라메터화된 타입 간의 이러한 종류의 관계를 "공변성(Covariant)"라고 합니다.

스칼라 공변성 문법

두 파라메터화된 타입 간의 공변성(covariance) 관계를 표현하기 위해, 스칼라는 + 기호를 타입 파라메터의 접두사로 붙여 정의합니다.

여기서 T는 타입 파라메터이고 + 기호는 스칼라 공변성을 정의합니다.

참고: 단순함을 위해, 여기서는 List를 사용하고 있습니다. 하지만, Set[+T], Ordered[+T] 등과 같은 유효한 스칼라 타입도 가능합니다.

공변성 예제

다음은 스칼라 공변성 서브타이핑 기술의 예를 들기 위한 스칼라 프로그램입니다.

class Animal[+T](val animial:T)

class Dog  
class Puppy extends Dog

class AnimalCarer(val dog:Animal[Dog])

object ScalaCovarianceTest{  
  def main(args: Array[String]) {
    val puppy = new Puppy
    val dog = new Dog

    val puppyAnimal:Animal[Puppy] = new Animal[Puppy](puppy)
    val dogAnimal:Animal[Dog] = new Animal[Dog](dog)

    val dogCarer = new AnimalCarer(dogAnimal)
    val puppyCarer = new AnimalCarer(puppyAnimal)

    println("Done.")
  }
}

Animal 클래스를 변성 표기법 즉, +T를 사용해 정의하였으므로, dogAnimal이나 그것의 서브 타입인 puppyAnimal을 전달하여 AnimalCarer라는 객체를 만들 수 있습니다.

Animal 클래스 정의에서 변성 표기법을 제거한다면, 다음과 같을 겁니다:

class Animal[T](val animial:T)  
// 나머지 코드는 위와 같습니다.

그러면 컴파일 되지 않습니다. 다음과 같은 컴파일 오류 메시지를 보게 될 겁니다:

Type mismatch, expected: Animal[Dog], found: Animal[Puppy]  

이런 종류의 문제를 해결하려면, 스칼라 공변성을 사용해야 합니다.

이러한 예제로 봤을 때, 공변성을 다음과 같이 이야기할 수 있습니다.

"Puppy가 Dog의 서브 타입이니까, Animal[Puppy]도 Animal[Dog]의 서브 타입이야. Animal[Dog]를 필요로 하는 곳에서는 Animal[Puppy]를 사용할 수 있어". 이것이 스칼라 공변성입니다.

스칼라의 반공변성(Contravariant)

ST의 서브 타입이면, List[T]는 List[S]의 서브 타입이다.

두 파라메터화된 타입 간의 이러한 종류의 상속 관계를 반공변성(contravariant)라고 합니다.

스칼라 반공변성 문법

두 파라메터화된 타입 간의 반공변성 관계를 표현하기 위해, 스칼라는 - 기호를 타입 파라메터의 접두사로 붙여 반공변성을 정의합니다.

반공변성 예제

다음은 스칼라 반공변성 서브 타이핑 기술의 예를 들기 위한 프로그램입니다.

abstract class Type [-T]{  
  def typeName : Unit
}

class SuperType extends Type[AnyVal]{  
  override def typeName: Unit = {
    println("SuperType")
  }
}
class SubType extends Type[Int]{  
  override def typeName: Unit = {
    println("SubType")
  }
}

class TypeCarer{  
  def display(t: Type[Int]){
    t.typeName
  }
}

object ScalaContravarianceTest {

  def main(args: Array[String]) {
    val superType = new SuperType
    val subType = new SubType

    val typeCarer = new TypeCarer

    typeCarer.display(subType)
    typeCarer.display(superType)
  }

}

참고: Type[-T]라고 반공변성을 정의했으므로, 잘 동작합니다. TypeCarer.display()Type[Int] 즉 서브 타입으로 정의되었지만, 스칼라 반공변 서브 타이핑 덕분에 Type[AnyVal]을 허용합니다.

Type[T]처럼 - 정의를 제거하면, 컴파일 오류가 발생합니다.

스칼라의 무공변성(invariant)

ST의 서브 타입일 때, List[S]와 List[T]가 상속 관계 혹은 서브 타이핑을 갖지 않는다. 즉 둘이 서로 관련이 없다는 것이다.

두 파라메터화된 타입 간의 이런 종류의 관계를 무공변성(invariant 혹은 non-variant)라고 합니다.

스칼라에서 기본적으로 제네릭 타입은 무공변성 관계입니다. 만약 +- 기호가 없이 파라메터화된 타입을 정의한다면, 무공변성입니다.

스칼라의 변성 표기란?

변성 표기란 타입 파라메터 앞에 + 혹은 -를 정의하는 것을 말합니다.

변경 표기 예제

+T-T를 스칼라에서 변경 표기라고 합니다.

스칼라 변성 요약

이 섹션에서는, 앞선 섹션에서 알아본 스칼라 변성 표기에 대한 3가지 개념에 대해서 알아보고자 합니다.

| 변성 타입 | 문법 | 설명 | | ------- |:---- -:| ----------------------------------------------------------------:| | 공변성 | `[+T]` | `S`가 `T`의 서브 타입이면, `List[S]`도 `List[T]`의 서브 타입입니다. | | 반공변성 | `[-T]` | `S`가 `T`의 서브 타입이면, `List[T]`도 `List[S]`의 서브 타입입니다. | | 무공변성 | `[T]` | `S`가 `T`의 서브 타입이면, `List[S]`와 `List[T]`는 서로 관련이 없습니다. |