맨위로가기

공변성과 반공변성 (컴퓨터 과학)

"오늘의AI위키"는 AI 기술로 일관성 있고 체계적인 최신 지식을 제공하는 혁신 플랫폼입니다.
"오늘의AI위키"의 AI를 통해 더욱 풍부하고 폭넓은 지식 경험을 누리세요.

1. 개요

공변성과 반공변성은 프로그래밍 언어의 자료형 체계에서 타입 간의 관계를 설명하는 개념이다. 공변성은 자료형 S가 T의 서브타입일 때, I가 I의 서브타입이 되는 성질을 의미하며, 반공변성은 S가 T의 서브타입일 때, I가 I의 서브타입이 되는 성질을 의미한다. 이변성은 공변성과 반공변성을 모두 만족하는 경우이며, 무공변성은 어느 쪽에도 해당하지 않는 경우이다. 이러한 개념은 제네릭 프로그래밍, 함수 타입, 객체 지향 언어의 상속 등에서 중요한 역할을 한다. 특히, 제네릭 자료 구조에서 요소형의 하위 형식 관계를 컨테이너에 어떻게 반영할지 결정하며, 함수 타입에서는 함수의 인자와 반환 타입의 서브타입 관계를 정의하는 데 사용된다. 객체 지향 언어에서는 메서드 오버라이딩 시 인자 타입과 반환 타입의 관계를 규정하여 타입 안전성을 보장하는 데 기여한다.

더 읽어볼만한 페이지

  • 제네릭 프로그래밍 - 다형성 (컴퓨터 과학)
    다형성은 프로그래밍 언어에서 이름이 여러 자료형에 사용되거나 객체가 여러 타입으로 취급되는 능력으로, 코드 재사용성과 유연성을 높이며 임시 다형성, 매개변수 다형성, 서브타이핑 등으로 분류되고 객체 지향 프로그래밍의 중요한 특징이며 정적, 동적 다형성으로 구현된다.
  • 제네릭 프로그래밍 - 표준 템플릿 라이브러리
    표준 템플릿 라이브러리(STL)는 알렉산더 스테파노프의 주도로 탄생한 C++ 라이브러리로서, 제네릭 프로그래밍을 지원하기 위해 컨테이너, 반복자, 알고리즘, 함수 객체 등의 핵심 구성 요소로 이루어져 있으며, HP에서 무료로 공개한 구현을 기반으로 다양한 구현체가 존재한다.
  • 함수형 프로그래밍 - 패턴 매칭
    패턴 매칭은 데이터 구조나 문자열에서 특정 패턴을 찾아 식별하는 기법으로, 다양한 프로그래밍 언어와 시스템에서 사용되며 데이터 필터링, 추출 및 선언적 프로그래밍에 중요한 역할을 수행한다.
  • 함수형 프로그래밍 - 익명 함수
    익명 함수는 이름이 없는 함수로, 람다 추상, 람다 함수, 람다 표현식, 화살표 함수 등으로 불리며, 함수형 프로그래밍 언어에서 람다식 형태로 많이 사용되고 고차 함수의 인수, 클로저, 커링 등에 활용되지만, 재귀 호출의 어려움이나 기능 제한과 같은 단점도 존재한다.
공변성과 반공변성 (컴퓨터 과학)
공변성 및 반공변성
개요
상세 내용
예시
함수형 프로그래밍
관련 개념

2. 정의

프로그래밍 언어의 자료형 체계에서, 자료형 생성자 `I`에 대한 자료형 규칙은 다음과 같다.



읽기 전용 데이터 유형(소스)은 공변적일 수 있고, 쓰기 전용 데이터 유형(싱크)은 반공변적일 수 있다. 소스와 싱크 역할을 모두 하는 변경 가능한 데이터 유형은 불변적이어야 한다.

배열 유형을 예로 들어, `Animal` 유형의 경우, "동물 배열"인 `Animal[]` 유형을 만들 수 있다. 이 배열이 요소의 읽기 및 쓰기를 모두 지원한다고 가정할 때, 다음과 같은 옵션을 고려할 수 있다.

  • 공변적: `Cat[]`은 `Animal[]`이다.
  • 반공변적: `Animal[]`은 `Cat[]`이다.
  • 불변적: `Animal[]`은 `Cat[]`가 아니고, `Cat[]`도 `Animal[]`이 아니다.


타입 오류를 피하려면 세 번째 선택 사항만 안전하다.

총칭화자료 구조에서의 공변성과 반공변성은 총칭화된 데이터 요소의 하위 형식 관계를 해당 컨테이너인 자료 구조의 하위 형식 관계에 어떻게 반영할지를 정의한다.

2. 1. 공변성 (Covariance)

프로그래밍 언어의 자료형 체계에서, 타입 매개변수 하나를 받는 타입 생성자를 I라고 할 때, S가 T의 서브타입이라면(S <: T), I{<}S{>}I{<}T{>}의 서브타입이 되는 성질을 '''공변'''(Covariance영어)이라고 한다. (I{<}S{>} <: I{<}T{>})[1]

예를 들어, C#에서 Cat이 Animal의 하위 형식이라면, IEnumerable은 IEnumerable의 하위 형식이 된다. IEnumerable는 T에 대해 '''공변'''이기 때문에 하위 형식 관계가 유지된다.

인터페이스 변성에 대한 형식 규칙은 형식 안전성을 보장한다. 읽기 전용 데이터 유형(소스)은 공변적일 수 있다.

배열 유형을 예로 들어 Animal 유형의 경우, "동물 배열"인 Animal[] 유형을 만들 수 있다.

다음과 같은 옵션이 있을 때,

  • 공변적: Cat[]은 Animal[]이다.


타입 오류를 피하기 위해서는 공변적 규칙은 안전하지 않다. Animal[]을 Cat[]으로 취급할 수도 없다. Animal[]에 Dog를 넣을 수 있어야 하는데, 공변적 배열에서는 실제 저장소가 고양이 배열일 수 있으므로 안전성을 보장할 수 없다.

자바와 C#의 초기 버전에는 매개변수 다형성이라고도 하는 제네릭이 포함되지 않았다. 자바와 C# 모두 배열 유형을 공변적으로 취급한다. 예를 들어 자바에서 String[]는 Object[]의 하위 유형이고, C#에서 string[]는 object[]의 하위 유형이다.

공변 배열은 배열에 쓰기 시 문제를 야기한다. 자바[4]와 C#은 각 배열 객체를 생성될 때 유형으로 표시하여 이를 처리한다. 배열에 값을 저장할 때마다 실행 환경은 값의 런타임 유형이 배열의 런타임 유형과 같은지 확인한다. 불일치가 발생하면 ArrayStoreException(자바)[4] 또는 ArrayTypeMismatchException(C#)이 발생한다.

제네릭이 추가되면서 자바[4]와 C#은 이제 공변성에 의존하지 않고 이러한 종류의 다형성 함수를 작성하는 방법을 제공한다.

총칭화자료 구조에서의 공변성은 총칭화된 데이터 요소의 하위 형식 관계를 해당 컨테이너인 자료 구조의 하위 형식 관계에 어떻게 반영할지를 정의한다. 총칭화 자료 구조는 제네릭 클래스로 구현되는 경우가 많다. List, Set, Map 등이 대표적이다.

총칭화 컨테이너는 Container와 같이 표기된다. 여기서 CatAnimal의 하위 형식으로 하면 ListList의 하위 형식 관계는 다음과 같다.

  • '''공변'''(covariant)은 요소형의 하위 형식 관계를 그대로 (정방향으로) 컨테이너에 반영한다. ListList의 하위 형식이 된다. 이는 List형 변수에 List인스턴스를 타입 안전하게 대입하고 싶을 때 등에 사용한다.


공변성은 클래스의 상속에서 자주 사용된다. 제네릭 클래스의 상속의 공변성은 총칭화된 데이터 구조의 공변성과 유사하게 사용된다.

프로그래밍 언어의 형식 시스템에서 형식 생성자 등이, 형의 순서 관계를 유지할 때 (≤로 정렬했을 때, 특수에서 일반 순서가 됨) '''공변'''이라고 한다.

클래스 계층 구조에서 메서드의 인수 및 반환값의 형을 검토할 때 중요하다. C++객체 지향 프로그래밍 언어에서는 클래스 ''B''가 클래스 ''A''의 서브타입일 때, ''B''의 멤버 함수는 모두 반환값의 형 집합이 ''A''와 같거나 더 작아야 한다. 즉, 반환값의 형은 공변이다.

전형적인 예를 제시한다.

  • 요소형으로부터 배열형을 구축하는 구문(형 구축자)은, 통상, 기본형에 대해 공변 또는 비변으로 간주된다. 공변으로 하는 경우, ''String'' ≤ ''Object''라면 ''ArrayOf(String)'' ≤ ''ArrayOf(Object)''이다. 단, 이는 배열이 불변인 경우에만 옳다 (정적 형 안전이다).
  • 일반적으로, 결과의 형은 공변이다.


객체 지향 프로그래밍에서는, 서브클래스에서 메서드를 오버라이드한 경우, 치환이 암묵적으로 이루어진다.

C# 3.0의 제네릭 형식 파라미터는 공변성을 지원하지 않는다. IEnumerable는 IEnumerable에 대입할 수 있을 것처럼 보이지만, 대입할 수 없다. C# 4.0에서는 이것이 지원되게 되었다. 또한, 일반적인 배열형은, .NET의 도입 이래, 항상 공변성을 계속 지원하고 있다 (엄밀하게 보장되는 것은 아니다. 배열의 대입이 정당하게 이루어져도, 실행 시에 예외가 발생할 가능성이 있다).

2. 2. 반공변성 (Contravariance)

ST의 서브타입일 때 (S <: T), I{<}T{>}I{<}S{>}의 서브타입이 되는 성질이다. (I{<}T{>} <: I{<}S{>})

프로그래밍 언어의
자료형 체계에서, 자료형 생성자 I에 대해, A ≤ B이면, I ≤ I 인 경우, 이 순서를 반전시키면 ''반공변적''이라고 한다.

예를 들어, C#에서 `Cat`이 `Animal`의 하위 형식이라면 `Action`은 `Action`의 하위 형식이다. `Action`는 `T`에 대해 '''반공변'''이기 때문에 하위 형식 관계가 반전된다.

C# 제네릭 인터페이스의 변성은 해당 형식 매개변수(0개 이상)에 `in` (반공변) 특성을 배치하여 선언된다.
[2] 예를 들어, 대리자 형식 `Func`는 `T` 형식의 '''반공변''' 입력 매개변수를 가진다.[3][2]

인터페이스 변성에 대한 형식 규칙은 형식 안전성을 보장한다. 예를 들어, `Action`는 `T` 형식의 인수를 예상하는 일급 함수를 나타내며,[2] 모든 종류의 동물을 처리할 수 있는 함수는 고양이만 처리할 수 있는 함수 대신 항상 사용할 수 있다.

쓰기 전용 데이터 유형(싱크)은 반공변적일 수 있다. 소스와 싱크 역할을 모두 하는 변경 가능한 데이터 유형은 불변적이어야 한다. 이러한 일반적인 현상을 설명하기 위해 배열 유형을 생각해 보자. `Animal` 유형의 경우, "동물 배열"인 `Animal[]` 유형을 만들 수 있다.

배열에서 읽는 클라이언트는 `Cat`을 예상하지만, `Animal[]`에는 예를 들어 `Dog`가 포함될 수 있기 때문에 반공변적 규칙은 안전하지 않다.

총칭화자료 구조에서의 반공변성은 총칭화된 데이터 요소의 하위 형식 관계를 해당 컨테이너인 자료 구조의 하위 형식 관계에 어떻게 반영할지를 정의한다. 총칭화 자료 구조는 제네릭클래스로 구현되는 경우가 많다. List, Set, Map 등이 대표적이다.

총칭화 컨테이너는 `Container`와 같이 표기된다. 여기서 `Cat`을 `Animal`의 하위 형식으로 하면 반공변(contravariant)은 요소형의 하위 형식 관계를 역방향으로 하여 컨테이너에 반영한다. `List`은 `List`의 하위 형식이 되지만, 이는 단지 타입 안전하지 않게 될 뿐이다. 반공변에서의 데이터 요소는 사상(일급 함수)이 되는 경우가 많으며, 사상의 정의역의 형식이 반공변으로 컨테이너에 반영된다. 특화된 정의역의 사상 컨테이너를, 일반화된 정의역의 사상 컨테이너로 대체하고 싶을 때 등에 사용한다.

공변성과 반공변성은 클래스의 상속에서 자주 사용된다. 제네릭 클래스의 상속의 공변성과 반공변성은 총칭화된 데이터 구조의 공변성과 반공변성과 유사하게 사용된다. 클래스의 메서드 상속의 공변성과 반공변성은 함수의 타입의 공변성과 반공변성과 유사하게 사용된다.

프로그래밍 언어의 형식 시스템에서 형식 생성자 등이 형의 순서 관계를 반전시킬 때, '''반변'''이라고 한다 (contravariant).

2. 3. 이변성 (Bivariance)

프로그래밍 언어의 자료형 체계에서, 어떤 타입 생성자가 공변성과 반공변성을 모두 만족하는 경우를 이변(Bivariance)이라고 한다. 다시 말해, 타입 매개변수 `S`가 `T`의 서브타입일 때, `I`가 `I`의 서브타입이면서 동시에 `I`가 `I`의 서브타입이 되는 성질을 의미한다.[1]

총칭화된 자료 구조에서 이변성은 요소형의 하위 형식 관계를 양방향으로 컨테이너에 반영한다. 예를 들어, `List`과 `List`이 서로 상호 교체 가능한 관계가 될 수 있다. 이는 특화된 정의역의 사상(함수) 컨테이너와 일반화된 정의역의 사상 컨테이너를 상호 교체해야 하는 상황 등에서 사용될 수 있다.

2. 4. 무공변성 (Invariant)

를 타입 매개변수 하나를 받는 타입 생성자라고 할 때, '''무공변'''(Invariant)은 공변하지도 반공변하지도 않는 것을 의미한다.[1]

예를 들어 C#에서 `IList`과 `IList` 모두 서로의 하위 형식이 아니다. 왜냐하면 `IList`는 `T`에 대해 '''불변'''이기 때문이다.

배열 유형을 생각해 볼 때, `Animal` 유형의 경우 "동물 배열"인 `Animal[]` 유형을 만들 수 있다. 이때 타입 오류를 피하기 위해서는 다음 중 세 번째 선택 사항만 안전하다.

  • 공변적: `Cat[]`은 `Animal[]`이다.
  • 반공변적: `Animal[]`은 `Cat[]`이다.
  • 불변적: `Animal[]`은 `Cat[]`가 아니고, `Cat[]`도 `Animal[]`이 아니다.


`Animal[]`에 `Dog`를 넣을 수 있어야 하고, 공변적 배열에서는 실제 저장소가 고양이 배열일 수 있으므로 공변적 규칙은 안전하지 않다. 마찬가지로, 모든 `Animal[]`을 `Cat[]`인 것처럼 취급할 수는 없으므로 반공변적 규칙도 안전하지 않다. 따라서 배열 생성자는 ''불변적''이어야 한다. 이는 변경 가능한 배열에만 해당되는 문제이며, 불변(읽기 전용) 배열의 경우 공변적 규칙이 안전하고, 쓰기 전용 배열의 경우 반공변적 규칙이 안전하다.

자바(Java)와 C#의 초기 버전에는 제네릭이 포함되지 않아 배열을 불변으로 만드는 것은 유용한 프로그램을 작성하는데 제약이 있었다. 예를 들어 배열을 섞는 함수는 모든 유형의 배열에서 작동하는 단일 함수를 작성할 수 있어야 하지만, 배열 유형이 불변으로 취급된다면 정확히 `Object[]` 유형의 배열에 대해서만 함수를 호출할 수 있어 문자열 배열 등을 섞을 수 없었다.

이러한 이유로 자바와 C# 모두 배열 유형을 공변적으로 취급한다. 그러나 이는 배열에 쓰기 시 문제를 야기했고, 각 배열 객체를 생성될 때 유형으로 표시하고 배열에 값을 저장할 때마다 런타임 유형을 확인하는 방식으로 처리했다.

제네릭이 추가되면서 자바와 C#은 공변성에 의존하지 않고 다형성 함수를 작성하는 방법을 제공한다.

총칭화자료 구조에서의 공변성과 반공변성은 총칭화된 데이터 요소의 하위 형식 관계를 해당 컨테이너인 자료 구조의 하위 형식 관계에 어떻게 반영할지를 정의한다. 총칭화 자료 구조는 제네릭클래스로 구현되는 경우가 많다. List, Set, Map 등이 대표적이다.

총칭화 컨테이너는 `Container`와 같이 표기된다. 여기서 '''비변'''(nonvariant)은 요소형의 하위 형식 관계를 컨테이너에 반영하지 않는다. `List`과 `List`은 별개의 클래스가 된다. 따라서 `List`형 변수에 `List`형 인스턴스를 대입하는 서브타이핑 등은 할 수 없다.

프로그래밍 언어의 형식 시스템에서 형식 생성자 등이 형의 순서 관계를 유지하거나 반전시키지 않을 때 '''불변'''이라고 한다.

3. 제네릭에서

C#과 Java 같은 언어들은 제네릭 프로그래밍을 지원하며, 이때 형식 매개변수 간의 하위 형식 관계를 정의하는 것이 공변성(covariance)과 반공변성(contravariance)이다.

예를 들어 C#에서 `Cat` 클래스가 `Animal` 클래스의 하위 형식일 때, 다음과 같은 관계가 성립한다.


  • `IEnumerable`은 `IEnumerable`의 하위 형식이다. `IEnumerable`는 `T`에 대해 공변이므로 하위 형식 관계가 유지된다.
  • `Action`은 `Action`의 하위 형식이다. `Action`는 `T`에 대해 반공변이므로 하위 형식 관계가 반전된다.
  • `IList`과 `IList`은 서로 하위 형식이 아니다. `IList`는 `T`에 대해 불변이기 때문이다.


C#에서는 제네릭 인터페이스의 형식 매개변수에 `out` (공변) 또는 `in` (반공변) 특성을 지정하여 변성을 선언한다.[2] 예를 들어, `IEnumerable`는 `T`에 대해 공변이고, `Action`는 반공변이며, `IList`는 불변이다.

두 개 이상의 형식 매개변수가 있는 형식은 각 매개변수에 대해 서로 다른 변성을 지정할 수 있다. 예를 들어, `Func`는 `T` 형식의 반공변 입력 매개변수와 `TResult` 형식의 공변 반환 값을 가진 함수를 나타낸다.[3][2]

컴파일러는 모든 형식이 주석과 일관되게 정의되고 사용되는지 확인하고, 그렇지 않으면 컴파일 오류를 발생시킨다.

인터페이스 변성에 대한 형식 규칙은 형식 안전성을 보장한다. 예를 들어, `Action`는 `T` 형식의 인수를 예상하는 함수를 나타내며,[2] 모든 종류의 동물을 처리할 수 있는 함수는 고양이만 처리할 수 있는 함수 대신 항상 사용될 수 있다.

읽기 전용 데이터 타입(소스)은 공변적일 수 있고, 쓰기 전용 데이터 타입(싱크)은 반공변적일 수 있다. 소스와 싱크 역할을 모두 하는 변경 가능한 데이터 타입은 불변이어야 한다.

이러한 개념을 설명하기 위해 배열을 예로 들어보자. `Animal` 타입에 대해 `Animal[]` 타입을 만들 수 있다. 이 배열은 읽기와 쓰기를 모두 지원한다.

다음과 같은 세 가지 옵션이 있다.

  • 공변적: `Cat[]`은 `Animal[]`이다.
  • 반공변적: `Animal[]`은 `Cat[]`이다.
  • 불변적: `Animal[]`은 `Cat[]`가 아니고, `Cat[]`도 `Animal[]`이 아니다.


타입 오류를 피하기 위해서는 세 번째 옵션(불변)만이 안전하다. 모든 `Animal[]`을 `Cat[]`처럼 취급할 수는 없다. 배열에서 읽는 클라이언트는 `Cat`을 예상하지만, `Animal[]`에는 `Dog`가 포함될 수 있기 때문이다. 따라서 반공변적 규칙은 안전하지 않다.

반대로, `Cat[]`을 `Animal[]`으로 취급할 수도 없다. `Animal[]`에 `Dog`를 넣을 수 있어야 하는데, 공변적 배열에서는 실제 저장소가 고양이 배열일 수 있으므로 안전성을 보장할 수 없다. 따라서 공변적 규칙도 안전하지 않으며, 배열 생성자는 불변이어야 한다. 이는 변경 가능한 배열에만 해당되는 문제이다. 불변(읽기 전용) 배열의 경우 공변적 규칙이 안전하고, 쓰기 전용 배열의 경우 반공변적 규칙이 안전하다.

Java와 C#의 초기 버전에는 제네릭이 없었다. 이러한 환경에서 배열을 불변으로 만드는 것은 다형성 프로그램을 배제한다. 예를 들어, 배열을 섞는 함수나 두 배열의 동일성을 테스트하는 함수를 작성할 때, 구현은 요소의 정확한 유형에 의존하지 않으므로 모든 유형의 배열에서 작동하는 단일 함수를 작성할 수 있어야 한다.

```java

boolean equalArrays(Object[] a1, Object[] a2);

void shuffleArray(Object[] a);

```

그러나 배열 유형이 불변으로 취급된다면 `Object[]` 유형의 배열에 대해서만 이러한 함수를 호출할 수 있다. 예를 들어, 문자열 배열을 섞을 수 없다.

따라서 Java와 C# 모두 배열 유형을 공변적으로 취급한다. Java에서 `String[]`는 `Object[]`의 하위 유형이고, C#에서 `string[]`는 `object[]`의 하위 유형이다.

하지만 공변 배열은 배열에 쓰기 시 문제를 야기한다. Java[4]와 C#은 각 배열 객체를 생성될 때 유형으로 표시하여 이를 처리한다. 배열에 값을 저장할 때마다 실행 환경은 값의 런타임 유형이 배열의 런타임 유형과 같은지 확인한다. 불일치가 발생하면 `ArrayStoreException`(Java)[4] 또는 `ArrayTypeMismatchException`(C#)이 발생한다.

```java

// a는 String의 단일 요소 배열입니다.

String[] a = new String[1];

// b는 Object의 배열입니다.

Object[] b = a;

// Integer를 b에 할당합니다. b가 실제로 Object의 배열이라면 가능하겠지만,

// 실제로 String의 배열이므로 java.lang.ArrayStoreException이 발생합니다.

b[0] = 1;

```

위의 예에서 배열(b)에서 안전하게 읽을 수 있다. 문제가 발생할 수 있는 것은 배열에 쓰기를 시도하는 것뿐이다.

이 접근 방식의 단점은 런타임 오류의 가능성이 남는다는 것이다. 또한 배열에 대한 각 쓰기에 추가적인 런타임 검사가 필요하므로 성능이 저하된다.

제네릭이 추가되면서 Java[4]와 C#은 이제 공변성에 의존하지 않고 이러한 종류의 다형성 함수를 작성하는 방법을 제공한다. 배열 비교 및 섞기 함수에는 다음과 같은 매개변수화된 유형을 지정할 수 있다.

```java

boolean equalArrays(T[] a1, T[] a2);

void shuffleArray(T[] a);

```

C#에서는 컬렉션에 읽기 전용 방식으로 액세스하도록 하기 위해 배열 `object[]`를 전달하는 대신 인터페이스 `IEnumerable`를 사용할 수 있다.

제네릭을 지원하는 프로그래밍 언어에서 프로그래머는 형식 시스템을 새로운 생성자로 확장할 수 있다. 예를 들어, C# 인터페이스 `IList`를 사용하면 `IList` 또는 `IList`과 같은 새로운 형식을 구성할 수 있다. 그러면 이러한 형식 생성자의 공변성은 무엇이어야 하는가 하는 의문이 제기된다.

두 가지 주요 접근 방식이 있다.

  • 선언 위치 공변성 주석 (예: C#): 형식 매개변수의 의도된 공변성을 사용하여 제네릭 형식의 정의에 주석을 단다.
  • 사용 위치 공변성 주석 (예: Java): 제네릭 형식이 인스턴스화되는 위치에 주석을 단다.

3. 1. 선언 위치 변성 주석 (Declaration-site variance annotations)

C#, Kotlin, Scala, OCaml과 같은 언어들은 제네릭 타입을 정의할 때 변성을 지정하는 방식을 사용한다. 이를 통해 타입 안정성을 유지하면서도 유연한 프로그래밍이 가능하다.

C#에서는 제네릭 인터페이스의 형식 매개변수에 `out` (공변) 또는 `in` (반공변) 특성을 사용하여 변성을 선언한다.[2] 예를 들어, `IEnumerable`는 `T`에 대해 공변적으로, `Action`는 `T`에 대해 반공변적으로 동작한다. 이러한 방식은 컴파일러가 모든 형식이 주석과 일관되게 사용되는지 확인하도록 하여 타입 안정성을 보장한다.

예를 들어, `IEnumerable`은 `IEnumerable`의 하위 형식이 되는데, 이는 `IEnumerable`가 `T`에 대해 공변이기 때문이다. 반대로, `Action`은 `Action`의 하위 형식이 되는데, 이는 `Action`가 `T`에 대해 반공변이기 때문이다. `IList`와 같이 `T`에 대해 불변인 경우에는 `IList`과 `IList` 사이에 하위 형식 관계가 성립하지 않는다.

C#, Kotlin, Scala, OCaml은 모두 선언 위치 변동성 주석을 지원하는 대표적인 언어들이다. C#은 인터페이스 유형에 대해서만 변동성 주석을 허용하는 반면, Kotlin, Scala, OCaml은 인터페이스 유형과 구체적인 데이터 유형 모두에 대해 변동성 주석을 허용한다.

3. 1. 1. 인터페이스

C#에서는 제네릭 인터페이스의 각 형식 매개변수를 공변(`out`), 반공변(`in`), 또는 불변(별도 표시 없음)으로 표시할 수 있다.[2] 예를 들어, 읽기 전용 이터레이터 인터페이스 `IEnumerator`를 정의하고, 해당 형식 매개변수를 공변(`out`)으로 선언할 수 있다.

```csharp

interface IEnumerator

{

T Current { get; }

bool MoveNext();

}

```

이 선언을 통해, `IEnumerator`는 형식 매개변수에서 공변으로 취급된다. 예를 들어, `IEnumerator`은 `IEnumerator`의 하위 형식이다.

형식 검사기는 인터페이스의 각 메서드 선언이 `in`/`out` 표시와 일치하는 방식으로 형식 매개변수를 언급하도록 강제한다. 즉, 공변으로 선언된 매개변수는 반공변 위치(반공변 형식 생성자의 홀수 개수 아래에서 발생하는 경우)에 나타나면 안 된다. 정확한 규칙[14][15]은 인터페이스의 모든 메서드 반환 형식은 '공변적으로 유효'해야 하고, 모든 메서드 매개변수 형식은 '반공변적으로 유효'해야 한다는 것이다. 여기서 'S-ly 유효'는 다음과 같이 정의된다.

  • 비제네릭 형식(클래스, 구조체, 열거형 등)은 공변적 및 반공변적으로 모두 유효하다.
  • 형식 매개변수 `T`는 `in`으로 표시되지 않은 경우 공변적으로 유효하며, `out`으로 표시되지 않은 경우 반공변적으로 유효하다.
  • 배열 형식 `A[]`는 `A`가 S-ly 유효한 경우 S-ly 유효하다. (이는 C#에 공변 배열이 있기 때문.)
  • 제네릭 형식 `G`은 각 매개변수 `Ai`에 대해 다음과 같은 경우 S-ly 유효하다.
  • `Ai`는 S-ly 유효하고, `G`의 ''i''번째 매개변수가 공변으로 선언되었거나,
  • `Ai`는 (S가 아님) 유효하고, `G`의 ''i''번째 매개변수가 반공변으로 선언되었거나,
  • `Ai`는 공변적 및 반공변적으로 모두 유효하고, `G`의 ''i''번째 매개변수가 불변으로 선언되었다.


이러한 규칙 적용 예시로, `IList` 인터페이스를 살펴보자.

```csharp

interface IList

{

void Insert(int index, T item);

IEnumerator GetEnumerator();

}

```

`Insert`의 매개변수 형식 `T`는 반공변적으로 유효해야 한다. 즉, 형식 매개변수 `T`는 `out`으로 표시될 수 없다. 마찬가지로, `GetEnumerator`의 결과 형식 `IEnumerator`는 공변적으로 유효해야 한다. 즉, (`IEnumerator`가 공변 인터페이스이므로) 형식 `T`는 공변적으로 유효해야 한다. 즉, 형식 매개변수 `T`는 `in`으로 표시될 수 없다. 이는 `IList` 인터페이스가 공변 또는 반공변으로 표시될 수 없음을 보여준다.

`IList`와 같은 제네릭 데이터 구조의 일반적인 경우, 이러한 제한은 `out` 매개변수가 구조체에서 데이터를 가져오는 메서드에만 사용될 수 있고, `in` 매개변수가 구조체에 데이터를 넣는 메서드에만 사용될 수 있음을 의미한다.

3. 1. 2. 데이터

스칼라, 코틀린, OCaml에서 불변 리스트(List) 타입은 공변적이다.[12] 즉, `List[Cat]`는 `List[Animal]`의 하위 타입이다.

3. 1. 3. 변성 추론

OCaml은 매개변수화된 구체적인 데이터 유형의 변성을 추론할 수 있지만, 추상 형식(인터페이스)의 변성은 프로그래머가 명시적으로 지정해야 한다.[17]

예를 들어, 함수를 래핑하는 OCaml 데이터 유형 `T`를 살펴보자.

```ocaml

type ('a, 'b) t = T of ('a -> 'b)

```

컴파일러는 `T`가 첫 번째 매개변수에서는 반변성이고 두 번째 매개변수에서는 공변성임을 자동으로 추론한다. 프로그래머는 또한 컴파일러가 충족하는지 확인할 명시적 주석을 제공할 수 있다. 따라서 다음 선언은 이전 선언과 동일하다.

```ocaml

type (-'a, +'b) t = T of ('a -> 'b)

```

OCaml의 명시적 주석은 인터페이스를 지정할 때 유용해진다. 예를 들어, 연관 테이블에 대한 표준 라이브러리 인터페이스 `Map.S`에는 맵 형식 생성자가 결과 형식에서 공변적이라는 주석이 포함되어 있다.

```ocaml

module type S =

sig

type key

type (+'a) t

val empty: 'a t

val mem: key -> 'a t -> bool

...

end

```

이렇게 하면 예를 들어 `cat IntMap.t`가 `animal IntMap.t`의 하위 유형인지 확인할 수 있다.

3. 2. 사용 위치 변성 주석 (Use-site variance annotations)

자바는 와일드카드를 사용하여 사용 시점에 변성을 지정한다.[4] 이는 바운디드 존재 타입의 제한된 형태이다. 예를 들어, `List` (Animal을 상속하는 모든 타입의 리스트) 또는 `List` (Animal의 슈퍼타입인 모든 타입의 리스트)와 같이 와일드카드를 사용하여 제네릭 타입을 더 유연하게 만들 수 있다.

3. 2. 1. Java 와일드카드

자바는 와일드카드를 통해 사용 시점 변성 주석을 제공하며, 이는 바운디드 존재 타입의 제한된 형태이다. 매개변수화된 타입은 상한 또는 하한과 함께 와일드카드 `?`로 인스턴스화될 수 있다. 예를 들어, `List` 또는 `List`가 있다. `List`와 같은 무제한 와일드카드는 `List`와 동일하다. 이러한 타입은 바운드를 만족하는 알려지지 않은 타입 `X`에 대한 `List`를 나타낸다.[4] 예를 들어, `l`의 타입이 `List`인 경우, 타입 검사기는

```java

Animal a = l.get(3);

```

`X` 타입이 `Animal`의 서브타입으로 알려져 있으므로 위 코드를 허용하지만,

```java

l.add(new Animal());

```

`Animal`이 반드시 `X`일 필요는 없으므로 위 코드는 타입 오류로 거부된다. 일반적으로, `I` 인터페이스가 주어지면, `I`에 대한 참조는 해당 메서드의 타입에서 `T`가 반공변적으로 발생하는 인터페이스의 메서드 사용을 금지한다. 반대로, `l`의 타입이 `List`인 경우 `l.add`를 호출할 수 있지만 `l.get`은 호출할 수 없다.

Java의 와일드카드 서브타이핑은 큐브로 시각화할 수 있습니다.


`List`은 `Animal`을 상속하는 모든 타입의 리스트를 나타내며, 공변성을 표현한다. `List`은 `Animal`의 슈퍼타입인 모든 타입의 리스트를 나타내며, 반공변성을 표현한다.

Java의 비-와일드카드 매개변수화된 타입은 불변이지만(예: `List`과 `List` 사이에는 서브타이핑 관계가 없음), 와일드카드 타입은 더 좁은 바운드를 지정하여 더 구체적으로 만들 수 있다. 예를 들어, `List`은 `List`의 서브타입이다. 이것은 와일드카드 타입이 "상한에서 공변적"이며 (또한 "하한에서 반공변적")임을 보여준다. 총체적으로, `C`와 같은 와일드카드 타입이 주어지면, 서브타입을 형성하는 세 가지 방법이 있다: `C` 클래스를 특수화하거나, 더 좁은 바운드 `T`를 지정하거나, 와일드카드 `?`를 특정 타입으로 대체하는 것이다.[4]

위의 세 가지 서브타이핑 형태 중 두 가지를 적용하면, 예를 들어 `List` 타입의 인수를 `List`을 기대하는 메서드에 전달하는 것이 가능해진다. 이것이 공변 인터페이스 타입에서 발생하는 표현력이다. `List` 타입은 `List`의 공변 메서드만 포함하는 인터페이스 타입으로 작동하지만, `List`의 구현자는 미리 정의할 필요가 없었다.

제네릭 데이터 구조 `IList`의 일반적인 경우, 공변 매개변수는 구조에서 데이터를 가져오는 메서드에 사용되고, 반공변 매개변수는 구조에 데이터를 넣는 메서드에 사용된다. 조슈아 블로흐(Joshua Bloch)의 책 ''Effective Java''에 나오는 Producer Extends, Consumer Super (PECS)에 대한 기억술은 공변성과 반공변성을 언제 사용해야 하는지 쉽게 기억할 수 있도록 해준다.[4]

와일드카드는 유연하지만 단점이 있다. 사용 시점 변성은 API 설계자가 인터페이스에 대한 타입 매개변수의 변성을 고려할 필요가 없다는 것을 의미하지만, 대신 더 복잡한 메서드 시그니처를 사용해야 하는 경우가 많다. 흔한 예는 `Comparable` 인터페이스와 관련이 있다.[4] 컬렉션에서 가장 큰 요소를 찾는 함수를 작성하려는 경우를 가정해 보자. 요소는 `compareTo` 메서드를 구현해야 하므로,[4] 첫 번째 시도는 다음과 같다.

```java

> T max(Collection coll);

```

그러나 이 타입은 충분히 일반적이지 않다. `Collection`에서 최댓값을 찾을 수 있지만, `Collection`에서는 찾을 수 없다. 문제는 `GregorianCalendar`가 `Comparable` 인터페이스를 구현하는 것이 아니라 (더 나은) `Comparable` 인터페이스를 구현한다는 것이다. Java에서는 C#과 달리 `Comparable`가 `Comparable`의 서브타입으로 간주되지 않는다. 대신 `max`의 타입을 수정해야 한다.

```java

> T max(Collection coll);

```

바운디드 와일드카드 `? super T`는 `max`가 `Comparable` 인터페이스에서 반공변 메서드만 호출한다는 정보를 전달한다.

`max` 메서드는 메서드 매개변수에 상한 와일드카드를 사용하여 더욱 변경할 수 있다.

```java

> T max(Collection coll);

4. 함수 타입에서

함수 타입에서 공변성과 반공변성은 함수 타입의 매개변수 타입과 반환 타입에 적용된다.

함수 ''f''가 ''g''보다 더 일반적인 유형의 인수를 받아들이고, ''g''보다 더 구체적인 유형을 반환하는 경우 ''g'' 대신 ''f'' 함수를 대체하는 것이 안전하다. 예를 들어, , , 타입의 함수는 이 예상되는 모든 곳에서 사용할 수 있다.

일반적인 규칙은 다음과 같다.

:(P_1 \rightarrow R_1) \leq (P_2 \rightarrow R_2) if P_1 \geq P_2 and R_1 \leq R_2.

추론 규칙 표기법을 사용하여 동일한 규칙을 다음과 같이 쓸 수 있다.

:\frac{P_1 \geq P_2 \quad R_1 \leq R_2}{(P_1 \rightarrow R_1) \leq (P_2 \rightarrow R_2)}

즉, 함수 타입 생성자는 ''매개변수(입력) 타입에 대해 반공변적''이며 ''반환(출력) 타입에 대해 공변적''이다. 이 규칙은 존 C. 레이놀즈에 의해 처음 공식적으로 명시되었고,[5] 루카 카르델리의 논문에서 더 널리 알려졌다.[6]

함수를 인수로 사용하는 함수를 다룰 때 이 규칙을 여러 번 적용할 수 있다. 예를 들어, 규칙을 두 번 적용하면 ((P_1 \to R) \to R) \le ((P_2 \to R) \to R) if P_1 \le P_2임을 알 수 있다. 다시 말해, ((A \to B) \to C) 타입은 A의 위치에서 ''공변적''이다.

함수 타입에서의 공변성과 반공변성은, 서브타입에서의 파라미터 타입과 리턴 타입의 일반화, 특수화를 제약하여 서브타이핑의 타입 안전성을 실현하기 위한 개념이 된다.

예를 들어 타입이 `Cat <: Animal`이라고 하면, 함수 `Animal->Animal`에 대한 함수 `Animal->Cat`의 대입은, 그 반대보다 안전하므로, `(Animal->Cat) <: (Animal->Animal)`이 권장된다. 파라미터 타입이 동일하다면, 리턴 타입의 서브타입 관계를 그대로 함수의 타입의 서브타입 관계에 반영할 수 있는데 이것은 공변이다.

파라미터 타입의 경우, 함수 `Animal->Animal`과 함수 `Cat->Animal` 중, `(Animal->Animal) <: (Cat->Animal)` 쪽이 타입 안전하다고 결론 내려졌다.[28][29] 이것은 반변이다.

파라미터 타입과 리턴 타입의 조합은 약간 복잡해진다. 여기서 파라미터 타입을 `Cat <: Animal`로 하고, 리턴 타입을 `수인 <: 동물`로 하면, 해당 함수의 타입에서는 `(Animal->수인) <: (Cat->동물)` 쪽이 타입 안전하다는 결론이 내려진다.

제네릭 함수에서도 사용되며, `S func[-T, +S] (T x, T y) { ... }`와 같이 구조화된다. `-`는 반변 기호, `+`는 공변 기호이다. 함수 `func`의 각 인스턴스는, 타입 인수를 반영한 서브타입 관계로 묶인다.

5. 객체 지향 언어의 상속에서

서브클래스가 슈퍼클래스의 메서드를 오버라이드할 때, 컴파일러는 메서드의 타입이 타입 안전(type safe영어)을 보장하는 방식으로 변형될 수 있는지 확인해야 한다. 무공변만을 허용하는 일부 언어에서는 인자 타입과 반환 타입 모두 슈퍼클래스의 인자 타입 및 반환 타입과 정확히 같아야 한다. 그러나 특별한 규칙을 따르면 오버라이드된 메서드가 더 유연한 타입을 갖도록 허용하면서도 타입 안전을 보장할 수 있다. 함수 유형에 대한 일반적인 서브타이핑 규칙에 따르면, 오버라이드된 메서드는 더욱 구체적인 타입을 반환해야 하고(반환 타입 공변), 더욱 일반적인 타입을 허용해야 한다.(인자 타입 반공변)

ClassB가 ClassA를 상속하는 서브클래스라고 할 때, 가능한 조합들을 UML로 나타내면 다음과 같다.

''무공변''. 오버라이딩된 메소드의 시그니처는 변하지 않았다. -- 타입 안전하다.


''공변 반환 타입''. 서브타이핑 관계는 ClassA와 ClassB 사이의 관계와 같은 방향이다. -- 타입 안전하다.


''반공변 인자 타입''. 서브타이핑 관계는 ClassA와 ClassB 사이의 관계와 반대 방향이다. -- 타입 안전하다.


''공변 인자 타입''. -- 이 경우에는 타입 안전하지 않다.


객체지향 프로그래밍 언어들의 메소드 오버라이딩 규칙은 다음과 같다.

프로그래밍 언어인자 타입반환 타입
C++ (1998년부터), 자바 (J2SE 5.0부터), D, C# (C# 9 이후)무공변공변
C# (C# 9 이전)무공변무공변
스칼라, 세이더반공변공변
에펠공변공변


5. 1. 공변 반환 타입 (Covariant method return type)

서브클래스가 슈퍼클래스의 메소드를 오버라이드할 때, 컴파일러는 메소드의 타입이 올바른지 확인해 타입 안전(type safe영어)을 보장해야 한다. 무공변만을 허용하는 일부 언어에서는 인자 타입과 반환 타입 모두 슈퍼클래스의 인자 타입 및 반환 타입과 정확히 같아야 한다. 그러나 특별한 규칙을 따르면 오버라이드된 메소드가 더 유연한 타입을 갖도록 허용하면서도 타입 안전을 보장할 수 있다. 함수 유형에 대한 일반적인 서브타이핑 규칙에 따르면, 오버라이드된 메소드는 더욱 구체적인 타입을 반환해야 하고(반환 타입 공변), 더욱 일반적인 타입을 허용해야 한다.(인자 타입 반공변)

객체지향 프로그래밍 언어들의 메소드 오버라이딩 규칙은 다음과 같다.

프로그래밍 언어인자 타입반환 타입
C++ (1998년부터), 자바 (J2SE 5.0부터), D무공변공변
C#무공변공변 (C# 9부터 - 이전까지는 무공변)
Scala반공변공변
Eiffel공변공변



UML diagram
upright


공변 반환 형식을 허용하는 언어에서 파생 클래스는 메서드를 재정의하여 더 구체적인 유형을 반환할 수 있다.

자바의 예:



class CatShelter extends AnimalShelter {

Cat getAnimalForAdoption() {

return new Cat();

}

}



주류 객체 지향 프로그래밍 언어 중 자바, C++, C# (9.0 버전부터[7])은 공변 반환 형식을 지원한다. 공변 반환 형식 추가는 1998년 표준 위원회에서 승인된 C++ 언어의 첫 번째 수정 사항 중 하나였다.[8] 스칼라D 또한 공변 반환 형식을 지원한다.

역대 객체 지향 프로그래밍 언어에서의 메서드 상속의 공변성 반공변성은 다음과 같이 변천해 왔다. 에펠(1986년 발표)의 파라미터형은 공변이었지만, 리스코프 치환 원칙(1994년 발표)으로 반변으로 노선이 수정되었다.

파라미터형반환형
20세기의 전형적인 OOP 언어동일동일
에펠공변공변
C++ (1998년부터), 자바(5.0부터), C#(9부터), D 언어동일공변
스칼라, Sather반변공변


5. 2. 반공변 인자 타입 (Contravariant method parameter type)

서브클래스가 슈퍼클래스의 메소드를 오버라이드할 때, 컴파일러는 메소드의 타입이 올바른지 확인해 타입 안전(type safe영어)을 보장해야 한다. 이때 오버라이드된 메소드는 더 일반적인 타입을 허용해야 한다. (인자 타입 반공변)

객체지향 프로그래밍 언어들의 메소드 오버라이딩 규칙은 다음과 같다.

프로그래밍 언어인자 타입반환 타입
C++, 자바, D무공변공변
C#무공변공변 (C# 9부터 - 이전까지는 무공변)
Scala반공변공변
Eiffel공변공변



예를 들어 C#에서 `Action`은 `Action`의 하위 형식이다. `Action`는 `T`에 대해 '''반공변'''이기 때문에 하위 형식 관계가 반전된다.

마찬가지로, 재정의된 메서드가 기본 클래스의 메서드보다 더 일반적인 인수를 허용하는 것은 타입 안전하다.

UML 다이어그램


```java

class CatShelter extends AnimalShelter {

void putAnimal(Object animal) {

// ...

}

}

```

이것을 실제로 허용하는 객체 지향 언어는 몇 개 없다 (예: mypy로 타입 검사를 수행할 때의 파이썬). C++, 자바 및 함수 오버로딩 및/또는 변수 섀도잉을 지원하는 대부분의 다른 언어는 이를 오버로드되거나 섀도잉된 이름을 가진 메서드로 해석한다.

그러나 세서는 공변성과 반공변성을 모두 지원했다. 재정의된 메서드의 호출 규칙은 ''out'' 매개변수 및 반환 값과 공변적이며, 일반 매개변수(모드 ''in'' 사용)와 반공변적이다.

역대 객체 지향 프로그래밍 언어에서의 메서드 상속의 공변성 반공변성은 다음과 같이 변천해 왔다. 에펠 (86년 발표)의 파라미터형은 공변이었지만, 리스코프 치환 원칙 (94년 발표)으로 반변으로 노선이 수정되었다.

파라미터형반환형
20세기의 전형적인 OOP 언어동일동일
에펠공변공변
C++ (98년부터), 자바 (5.0부터), C# (9부터), D 언어동일공변
스칼라, 세더반변공변


5. 3. 공변 인자 타입 (Covariant method parameter type)

주류 프로그래밍 언어 중 에펠과 Dart[9]는 오버라이딩(overriding) 메서드의 매개변수가 슈퍼클래스의 메서드보다 '더' 구체적인 타입을 갖도록 허용한다(매개변수 타입 공변성). 따라서 다음과 같은 Dart 코드는 타입 검사를 통과하며, `putAnimal`은 기본 클래스의 메서드를 오버라이딩한다.

UML diagram
upright


```dart

class CatShelter extends AnimalShelter {

void putAnimal(covariant Cat animal) {

// ...

}

}

```

이는 타입 안전하지 않다. `CatShelter`를 `AnimalShelter`로 업캐스팅(up-casting)함으로써, 고양이가 아닌 개를 고양이 보호소에 넣으려고 시도할 수 있다. 이는 `CatShelter` 매개변수 제약 조건을 충족하지 못하며 런타임 오류가 발생한다. 타입 안전성의 부족(에펠 커뮤니티에서는 "catcall 문제"로 알려져 있으며, 여기서 "cat" 또는 "CAT"는 변경된 가용성 또는 타입(Changed Availability or Type)을 의미한다)은 오랫동안 지속된 문제였다. 수년에 걸쳐, 전역 정적 분석, 로컬 정적 분석, 그리고 새로운 언어 기능의 다양한 조합이 이를 해결하기 위해 제안되었으며,[10][11] 일부 에펠 컴파일러에서 구현되었다.

타입 안전성 문제에도 불구하고, 에펠 설계자들은 공변 매개변수 타입이 실제 세계의 요구 사항을 모델링하는 데 매우 중요하다고 간주한다.[11] 고양이 보호소는 일반적인 현상을 보여준다. 즉, '일종의' 동물 보호소이지만 '추가적인 제약'이 있으며, 상속과 제한된 매개변수 타입을 사용하여 이를 모델링하는 것이 합리적이다. 이러한 상속 사용을 제안하면서 에펠 설계자들은 리스코프 치환 원칙을 거부하는데, 이 원칙은 하위 클래스의 객체는 항상 슈퍼클래스의 객체보다 덜 제한적이어야 한다고 명시한다.

메서드 매개변수에서 공변성을 허용하는 또 다른 주류 언어의 예는 클래스 생성자와 관련하여 PHP이다. 다음 예제에서, `__construct()` 메서드는 상위 메서드 매개변수에 대해 공변적인 메서드 매개변수에도 불구하고 허용된다. 이 메서드가 `__construct()`가 아니라면 오류가 발생한다.

```php

interface AnimalInterface {}

interface DogInterface extends AnimalInterface {}

class Dog implements DogInterface {}

class Pet

{

public function __construct(AnimalInterface $animal) {}

}

class PetDog extends Pet

{

public function __construct(DogInterface $dog)

{

parent::__construct($dog);

}

}

```

공변 매개변수가 유용해 보이는 또 다른 예는 소위 이진 메서드, 즉 매개변수가 메서드가 호출된 객체와 동일한 타입일 것으로 예상되는 메서드이다. 예시는 `compareTo` 메서드이다. `a.compareTo(b)`는 `a`가 어떤 정렬에서 `b`보다 먼저 오는지 아니면 뒤에 오는지 확인하지만, 예를 들어 두 개의 유리수를 비교하는 방법은 두 개의 문자열을 비교하는 방법과 다를 것이다. 이진 메서드의 다른 일반적인 예로는 동등성 테스트, 산술 연산, 그리고 부분 집합 및 합집합과 같은 집합 연산이 있다.

구형 버전의 Java에서는 비교 메서드가 `Comparable` 인터페이스로 지정되었다.

```java

interface Comparable {

int compareTo(Object o);

}

```

이의 단점은 메서드가 `Object` 타입의 인수를 사용하도록 지정된다는 것이다. 전형적인 구현은 먼저 이 인수를 다운캐스팅한다(예상된 타입이 아닌 경우 오류 발생):

```java

class RationalNumber implements Comparable {

int numerator;

int denominator;

// ...

public int compareTo(Object other) {

RationalNumber otherNum = (RationalNumber)other;

return Integer.compare(numerator * otherNum.denominator,

otherNum.numerator * denominator);

}

}

```

공변 매개변수가 있는 언어에서는 `compareTo`에 대한 인수가 원하는 타입 `RationalNumber`를 직접 받을 수 있으며, 타입 캐스팅을 숨긴다. (물론, 만약 `compareTo`가 예를 들어 `String`에 대해 호출되면 여전히 런타임 오류가 발생한다.)

5. 4. 프로그래밍 언어별 메서드 오버라이딩 규칙 요약

프로그래밍 언어인자 타입반환 타입
C++ (1998년부터), 자바 (J2SE 5.0부터), D, C# (C# 9 이후)무공변공변
C# (C# 9 이전)무공변무공변
스칼라, 세이더반공변공변
에펠공변공변


6. 배열

배열 유형을 생각해 보자. Animal영어 유형의 경우, "동물 배열"인 Animal[]영어 유형을 만들 수 있다. 이 배열은 요소의 읽기와 쓰기를 모두 지원한다.

타입 오류를 피하기 위해, 다음 세 가지 선택 사항 중 세 번째 선택 사항만 안전하다.

경우설명
공변적Cat[]영어은 Animal[]영어이다.
반공변적Animal[]영어은 Cat[]영어이다.
불변적Animal[]영어은 Cat[]영어가 아니고, Cat[]영어도 Animal[]영어이 아니다.



Animal[]영어을 Cat[]영어인 것처럼 취급할 수는 없다. 배열에서 읽는 클라이언트는 Cat영어을 예상하지만, Animal[]영어에는 Dog영어가 포함될 수 있기 때문이다. 따라서 반공변적 규칙은 안전하지 않다.

반대로, Cat[]영어을 Animal[]영어으로 취급할 수도 없다. Animal[]영어에 Dog영어를 넣을 수 있어야 하는데, 공변적 배열에서는 실제 저장소가 고양이 배열일 수 있으므로 안전성을 보장할 수 없다. 따라서 공변적 규칙도 안전하지 않으며, 배열 생성자는 ''불변적''이어야 한다. 이는 변경 가능한 배열에만 해당되는 문제로, 불변(읽기 전용) 배열의 경우 공변적 규칙이 안전하고, 쓰기 전용 배열의 경우 반공변적 규칙이 안전하다.

자바와 C#의 초기 버전에는 제네릭이 없었다. 이 환경에서 배열을 불변으로 만드는 것은 다형성 프로그램을 배제한다. 예를 들어, 배열을 섞는 함수는 다음과 같이 작성할 수 있다.

```java

void shuffleArray(Object[] a);

```

그러나 배열 유형이 불변으로 취급된다면 Object[]영어 유형의 배열에 대해서만 이 함수를 호출할 수 있어, 예를 들어 문자열 배열을 섞을 수 없게 된다.

따라서 자바와 C# 모두 배열 유형을 공변적으로 취급한다. 예를 들어 자바에서 String[]영어는 Object[]영어의 하위 유형이고, C#에서 string[]영어는 object[]영어의 하위 유형이다.

하지만 공변 배열은 배열에 쓰기 시 문제를 야기한다. 자바[4]와 C#은 각 배열 객체를 생성될 때 유형으로 표시하여 이를 처리한다. 배열에 값을 저장할 때마다 실행 환경은 값의 런타임 유형이 배열의 런타임 유형과 같은지 확인하고, 불일치가 발생하면 ArrayStoreException영어(자바)[4] 또는 ArrayTypeMismatchException영어(C#)이 발생한다.

```java

// a는 String의 단일 요소 배열입니다.

String[] a = new String[1];

// b는 Object의 배열입니다.

Object[] b = a;

// Integer를 b에 할당합니다. b가 실제로 Object의 배열이라면 가능하겠지만,

// 실제로 String의 배열이므로 java.lang.ArrayStoreException이 발생합니다.

b[0] = 1;

```

이 접근 방식은 런타임 오류의 가능성을 남겨두고, 배열에 대한 각 쓰기에 추가적인 런타임 검사가 필요하여 성능이 저하된다는 단점이 있다.

제네릭이 추가되면서, 자바[4]와 C#은 이제 공변성에 의존하지 않고 다형성 함수를 작성하는 방법을 제공한다. 배열 섞기 함수는 다음과 같은 매개변수화된 유형을 지정할 수 있다.

```java

void shuffleArray(T[] a);

```

또는 C#에서는, 컬렉션에 읽기 전용 방식으로 액세스하도록 하기 위해 배열 object[]영어를 전달하는 대신 인터페이스 IEnumerable영어를 사용할 수 있다.

7. 범주론과의 관계

이 용어들은 범주론의 공변 및 반공변 펀터 개념에서 유래되었다.[1] 객체를 자료형으로, 사상을 하위 형식 관계 ≤로 나타내는 범주 C를 생각할 수 있다. (이는 부분 순서 집합을 범주로 간주하는 방법의 예시이다.) 함수형 자료형 생성자는 두 개의 자료형 ''p''와 ''r''을 받아 새로운 자료형 ''p'' → ''r''을 생성한다. 이는 C^2의 객체를 C의 객체로 만드는 것이다. 함수형 자료형에 대한 하위 형식 규칙에 따라 이 연산은 첫 번째 매개변수에 대해 ≤를 반전시키고 두 번째 매개변수에 대해 이를 유지하므로, 첫 번째 매개변수에서는 반공변 펀터이고 두 번째 매개변수에서는 공변 펀터이다.

서브타입 관계를 사상으로, 타입의 집합 ''C''를 범주로 볼 수 있다.[29] 프로그램에서 타입 ''p''의 값을 받아 타입 ''r''의 값을 반환하는 함수를 정의하면, 타입 시스템에서는 함수의 타입 「''p'' →''r'' 」을 생성한다. 이러한 함수의 타입 구문(타입 생성자)은 두 개의 타입으로부터 새로운 타입을 생성하는 사상 ''F'' : ''C'' ×''C'' → ''C''로 생각할 수 있다.

함수 타입의 규칙으로서 정적 타입 안전한[29] 규칙을 따르면, 이 사상 ''F''는 첫 번째 인수에 대해서는 서브타입 관계를 반전시켜 사상시키고(반변 함자에 해당), 두 번째 인수에 대해서는 서브타입 관계를 같은 형태로 사상시킨다(공변 함자에 해당).[1]

참조

[1] 문서
[2] 서적 C# in Depth Manning 2019-03-23
[3] 웹사이트 Func Delegate http://msdn.microsof[...]
[4] 서적 "Effective Java: Programming Language Guide" Addison-Wesley
[5] 간행물 The Essence of Algol https://www.cs.cmu.e[...] North-Holland
[6] 간행물 A semantics of multiple inheritance http://lucacardelli.[...] Springer
[7] 뉴스 C# 9.0 on the record https://devblogs.mic[...]
[8] 뉴스 What's New in Standard C++? http://www.drdobbs.c[...]
[9] 웹사이트 Fixing Common Type Problems https://www.dartlang[...]
[10] 간행물 Static Typing http://se.ethz.ch/~m[...] 1995-10
[11] 웹사이트 Type-safe covariance: Competent compilers can catch all catcalls http://se.ethz.ch/~m[...] 2003-04
[12] 간행물 Getting Class Correctness and System Correctness Equivalent - How to Get Covariance Right
[13] 학술지 Covariance and contravariance: conflict without a cause 1995-05
[14] 웹사이트 Exact rules for variance validity http://blogs.msdn.co[...] 2009-12-03
[15] 서적 ECMA International Standard ECMA-335 Common Language Infrastructure (CLI) http://www.ecma-inte[...] 2012-06
[16] 간행물 Taming the wildcards: combining definition- and use-site variance ACM 2011
[17] 웹사이트 Covariance and Contravariance in C# Part Seven: Why Do We Need A Syntax At All? http://blogs.msdn.co[...] 2007-10-29
[18] 웹사이트 The Scala 2.8 Collections API http://www.scala-lan[...] 2010-09-07
[19] 웹사이트 The Closures Controversy [video] http://parleys.com/p[...] 2007-11
[20] 간행물 Scalable component abstractions http://lampwww.epfl.[...] ACM
[21] 웹사이트 The Purpose of Scala's Type System: A Conversation with Martin Odersky, Part III http://www.artima.co[...] 2009-05-18
[22] 간행물 Mixed-Site Variance http://www.cs.cornel[...]
[23] 간행물 On Variance-Based Subtyping for Parametric Types
[24] 간행물 Unifying Genericity: Combining the Benefits of Virtual Types and Parameterized Classes Springer 1999
[25] 웹사이트 The Java™ Tutorials, Generics (Updated), Unbounded Wildcards https://docs.oracle.[...]
[26] 간행물 Taming wildcards in Java's type system http://www.cs.cornel[...]
[27] 간행물 Java generics are turing complete
[28] 간행물 The Essence of Algol https://www.cs.cmu.e[...] North-Holland
[29] 학술지 A semantics of multiple inheritance 1988-02



본 사이트는 AI가 위키백과와 뉴스 기사,정부 간행물,학술 논문등을 바탕으로 정보를 가공하여 제공하는 백과사전형 서비스입니다.
모든 문서는 AI에 의해 자동 생성되며, CC BY-SA 4.0 라이선스에 따라 이용할 수 있습니다.
하지만, 위키백과나 뉴스 기사 자체에 오류, 부정확한 정보, 또는 가짜 뉴스가 포함될 수 있으며, AI는 이러한 내용을 완벽하게 걸러내지 못할 수 있습니다.
따라서 제공되는 정보에 일부 오류나 편향이 있을 수 있으므로, 중요한 정보는 반드시 다른 출처를 통해 교차 검증하시기 바랍니다.

문의하기 : help@durumis.com