공변성과 반공변성 (컴퓨터 과학)
"오늘의AI위키"의 AI를 통해 더욱 풍부하고 폭넓은 지식 경험을 누리세요.
1. 개요
공변성과 반공변성은 프로그래밍 언어의 자료형 체계에서 타입 간의 관계를 설명하는 개념이다. 공변성은 자료형 S가 T의 서브타입일 때, I가 I의 서브타입이 되는 성질을 의미한다. 이변성은 공변성과 반공변성을 모두 만족하는 경우이며, 무공변성은 어느 쪽에도 해당하지 않는 경우이다. 이러한 개념은 제네릭 프로그래밍, 함수 타입, 객체 지향 언어의 상속 등에서 중요한 역할을 한다. 특히, 제네릭 자료 구조에서 요소형의 하위 형식 관계를 컨테이너에 어떻게 반영할지 결정하며, 함수 타입에서는 함수의 인자와 반환 타입의 서브타입 관계를 정의하는 데 사용된다. 객체 지향 언어에서는 메서드 오버라이딩 시 인자 타입과 반환 타입의 관계를 규정하여 타입 안전성을 보장하는 데 기여한다.
더 읽어볼만한 페이지
- 제네릭 프로그래밍 - 다형성 (컴퓨터 과학)
다형성은 프로그래밍 언어에서 이름이 여러 자료형에 사용되거나 객체가 여러 타입으로 취급되는 능력으로, 코드 재사용성과 유연성을 높이며 임시 다형성, 매개변수 다형성, 서브타이핑 등으로 분류되고 객체 지향 프로그래밍의 중요한 특징이며 정적, 동적 다형성으로 구현된다. - 제네릭 프로그래밍 - 표준 템플릿 라이브러리
표준 템플릿 라이브러리(STL)는 알렉산더 스테파노프의 주도로 탄생한 C++ 라이브러리로서, 제네릭 프로그래밍을 지원하기 위해 컨테이너, 반복자, 알고리즘, 함수 객체 등의 핵심 구성 요소로 이루어져 있으며, HP에서 무료로 공개한 구현을 기반으로 다양한 구현체가 존재한다. - 함수형 프로그래밍 - 패턴 매칭
패턴 매칭은 데이터 구조나 문자열에서 특정 패턴을 찾아 식별하는 기법으로, 다양한 프로그래밍 언어와 시스템에서 사용되며 데이터 필터링, 추출 및 선언적 프로그래밍에 중요한 역할을 수행한다. - 함수형 프로그래밍 - 익명 함수
익명 함수는 이름이 없는 함수로, 람다 추상, 람다 함수, 람다 표현식, 화살표 함수 등으로 불리며, 함수형 프로그래밍 언어에서 람다식 형태로 많이 사용되고 고차 함수의 인수, 클로저, 커링 등에 활용되지만, 재귀 호출의 어려움이나 기능 제한과 같은 단점도 존재한다.
공변성과 반공변성 (컴퓨터 과학) | |
---|---|
공변성 및 반공변성 | |
개요 | |
상세 내용 | |
예시 | |
함수형 프로그래밍 | |
관련 개념 | |
2. 정의
프로그래밍 언어의 자료형 체계에서, 자료형 생성자 `I`에 대한 자료형 규칙은 다음과 같다.
- 자료형의 순서(≤)를 보존하면, 즉 더 구체적인 자료형에서 더 일반적인 자료형으로 자료형을 정렬하면 ''공변적''이다. 만약 `A ≤ B`이면, `I
≤ I `이다. - 이 순서를 반전시키면 ''반공변적''이다. 만약 `A ≤ B`이면, `I
≤ I `이다. - 이 두 가지 모두 적용되면 ''이변적''이다(즉, `A ≤ B`이면, `I
≡ I `이다).[1] - 공변적, 반공변적 또는 이변적이면 ''변형적''이다.
- 변형적이지 않으면 ''불변적'' 또는 ''비변형적''이다.
읽기 전용 데이터 유형(소스)은 공변적일 수 있고, 쓰기 전용 데이터 유형(싱크)은 반공변적일 수 있다. 소스와 싱크 역할을 모두 하는 변경 가능한 데이터 유형은 불변적이어야 한다.
배열 유형을 예로 들어, `Animal` 유형의 경우, "동물 배열"인 `Animal[]` 유형을 만들 수 있다. 이 배열이 요소의 읽기 및 쓰기를 모두 지원한다고 가정할 때, 다음과 같은 옵션을 고려할 수 있다.
- 공변적: `Cat[]`은 `Animal[]`이다.
- 반공변적: `Animal[]`은 `Cat[]`이다.
- 불변적: `Animal[]`은 `Cat[]`가 아니고, `Cat[]`도 `Animal[]`이 아니다.
타입 오류를 피하려면 세 번째 선택 사항만 안전하다.
총칭화자료 구조에서의 공변성과 반공변성은 총칭화된 데이터 요소의 하위 형식 관계를 해당 컨테이너인 자료 구조의 하위 형식 관계에 어떻게 반영할지를 정의한다.
2. 1. 공변성 (Covariance)
프로그래밍 언어의 자료형 체계에서, 타입 매개변수 하나를 받는 타입 생성자를 I라고 할 때, S가 T의 서브타입이라면(), 는 의 서브타입이 되는 성질을 '''공변'''(Covariance영어)이라고 한다. ()[1]예를 들어, C#에서 Cat이 Animal의 하위 형식이라면, IEnumerable
인터페이스 변성에 대한 형식 규칙은 형식 안전성을 보장한다. 읽기 전용 데이터 유형(소스)은 공변적일 수 있다.
배열 유형을 예로 들어 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
와 같이 표기된다. 여기서 Cat
을 Animal
의 하위 형식으로 하면 List
과 List
의 하위 형식 관계는 다음과 같다.- '''공변'''(covariant)은 요소형의 하위 형식 관계를 그대로 (정방향으로) 컨테이너에 반영한다.
List
은List
의 하위 형식이 된다. 이는List
형 변수에List
형 인스턴스를 타입 안전하게 대입하고 싶을 때 등에 사용한다.
공변성은 클래스의 상속에서 자주 사용된다. 제네릭 클래스의 상속의 공변성은 총칭화된 데이터 구조의 공변성과 유사하게 사용된다.
프로그래밍 언어의 형식 시스템에서 형식 생성자 등이, 형의 순서 관계를 유지할 때 (≤로 정렬했을 때, 특수에서 일반 순서가 됨) '''공변'''이라고 한다.
클래스 계층 구조에서 메서드의 인수 및 반환값의 형을 검토할 때 중요하다. C++객체 지향 프로그래밍 언어에서는 클래스 ''B''가 클래스 ''A''의 서브타입일 때, ''B''의 멤버 함수는 모두 반환값의 형 집합이 ''A''와 같거나 더 작아야 한다. 즉, 반환값의 형은 공변이다.
전형적인 예를 제시한다.
- 요소형으로부터 배열형을 구축하는 구문(형 구축자)은, 통상, 기본형에 대해 공변 또는 비변으로 간주된다. 공변으로 하는 경우, ''String'' ≤ ''Object''라면 ''ArrayOf(String)'' ≤ ''ArrayOf(Object)''이다. 단, 이는 배열이 불변인 경우에만 옳다 (정적 형 안전이다).
- 일반적으로, 결과의 형은 공변이다.
객체 지향 프로그래밍에서는, 서브클래스에서 메서드를 오버라이드한 경우, 치환이 암묵적으로 이루어진다.
C# 3.0의 제네릭 형식 파라미터는 공변성을 지원하지 않는다. IEnumerable
2. 2. 반공변성 (Contravariance)
가 의 서브타입일 때 (), 가 의 서브타입이 되는 성질이다. ()프로그래밍 언어의 자료형 체계에서, 자료형 생성자 에 대해,
A ≤ B
이면, I ≤ I
인 경우, 이 순서를 반전시키면 ''반공변적''이라고 한다.예를 들어, C#에서 `Cat`이 `Animal`의 하위 형식이라면 `Action
C# 제네릭 인터페이스의 변성은 해당 형식 매개변수(0개 이상)에 `in` (반공변) 특성을 배치하여 선언된다.[2] 예를 들어, 대리자 형식 `Func
인터페이스 변성에 대한 형식 규칙은 형식 안전성을 보장한다. 예를 들어, `Action
쓰기 전용 데이터 유형(싱크)은 반공변적일 수 있다. 소스와 싱크 역할을 모두 하는 변경 가능한 데이터 유형은 불변적이어야 한다. 이러한 일반적인 현상을 설명하기 위해 배열 유형을 생각해 보자. `Animal` 유형의 경우, "동물 배열"인 `Animal[]` 유형을 만들 수 있다.
배열에서 읽는 클라이언트는 `Cat`을 예상하지만, `Animal[]`에는 예를 들어 `Dog`가 포함될 수 있기 때문에 반공변적 규칙은 안전하지 않다.
총칭화자료 구조에서의 반공변성은 총칭화된 데이터 요소의 하위 형식 관계를 해당 컨테이너인 자료 구조의 하위 형식 관계에 어떻게 반영할지를 정의한다. 총칭화 자료 구조는 제네릭클래스로 구현되는 경우가 많다. List, Set, Map 등이 대표적이다.
총칭화 컨테이너는 `Container
공변성과 반공변성은 클래스의 상속에서 자주 사용된다. 제네릭 클래스의 상속의 공변성과 반공변성은 총칭화된 데이터 구조의 공변성과 반공변성과 유사하게 사용된다. 클래스의 메서드 상속의 공변성과 반공변성은 함수의 타입의 공변성과 반공변성과 유사하게 사용된다.
프로그래밍 언어의 형식 시스템에서 형식 생성자 등이 형의 순서 관계를 반전시킬 때, '''반변'''이라고 한다 (contravariant).
2. 3. 이변성 (Bivariance)
프로그래밍 언어의 자료형 체계에서, 어떤 타입 생성자가 공변성과 반공변성을 모두 만족하는 경우를 이변(Bivariance)이라고 한다. 다시 말해, 타입 매개변수 `S`가 `T`의 서브타입일 때, `I총칭화된 자료 구조에서 이변성은 요소형의 하위 형식 관계를 양방향으로 컨테이너에 반영한다. 예를 들어, `List
2. 4. 무공변성 (Invariant)
를 타입 매개변수 하나를 받는 타입 생성자라고 할 때, '''무공변'''(Invariant)은 공변하지도 반공변하지도 않는 것을 의미한다.[1]예를 들어 C#에서 `IList
배열 유형을 생각해 볼 때, `Animal` 유형의 경우 "동물 배열"인 `Animal[]` 유형을 만들 수 있다. 이때 타입 오류를 피하기 위해서는 다음 중 세 번째 선택 사항만 안전하다.
- 공변적: `Cat[]`은 `Animal[]`이다.
- 반공변적: `Animal[]`은 `Cat[]`이다.
- 불변적: `Animal[]`은 `Cat[]`가 아니고, `Cat[]`도 `Animal[]`이 아니다.
`Animal[]`에 `Dog`를 넣을 수 있어야 하고, 공변적 배열에서는 실제 저장소가 고양이 배열일 수 있으므로 공변적 규칙은 안전하지 않다. 마찬가지로, 모든 `Animal[]`을 `Cat[]`인 것처럼 취급할 수는 없으므로 반공변적 규칙도 안전하지 않다. 따라서 배열 생성자는 ''불변적''이어야 한다. 이는 변경 가능한 배열에만 해당되는 문제이며, 불변(읽기 전용) 배열의 경우 공변적 규칙이 안전하고, 쓰기 전용 배열의 경우 반공변적 규칙이 안전하다.
자바(Java)와 C#의 초기 버전에는 제네릭이 포함되지 않아 배열을 불변으로 만드는 것은 유용한 프로그램을 작성하는데 제약이 있었다. 예를 들어 배열을 섞는 함수는 모든 유형의 배열에서 작동하는 단일 함수를 작성할 수 있어야 하지만, 배열 유형이 불변으로 취급된다면 정확히 `Object[]` 유형의 배열에 대해서만 함수를 호출할 수 있어 문자열 배열 등을 섞을 수 없었다.
이러한 이유로 자바와 C# 모두 배열 유형을 공변적으로 취급한다. 그러나 이는 배열에 쓰기 시 문제를 야기했고, 각 배열 객체를 생성될 때 유형으로 표시하고 배열에 값을 저장할 때마다 런타임 유형을 확인하는 방식으로 처리했다.
제네릭이 추가되면서 자바와 C#은 공변성에 의존하지 않고 다형성 함수를 작성하는 방법을 제공한다.
총칭화자료 구조에서의 공변성과 반공변성은 총칭화된 데이터 요소의 하위 형식 관계를 해당 컨테이너인 자료 구조의 하위 형식 관계에 어떻게 반영할지를 정의한다. 총칭화 자료 구조는 제네릭클래스로 구현되는 경우가 많다. List, Set, Map 등이 대표적이다.
총칭화 컨테이너는 `Container
프로그래밍 언어의 형식 시스템에서 형식 생성자 등이 형의 순서 관계를 유지하거나 반전시키지 않을 때 '''불변'''이라고 한다.
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
두 개 이상의 형식 매개변수가 있는 형식은 각 매개변수에 대해 서로 다른 변성을 지정할 수 있다. 예를 들어, `Func
컴파일러는 모든 형식이 주석과 일관되게 정의되고 사용되는지 확인하고, 그렇지 않으면 컴파일 오류를 발생시킨다.
인터페이스 변성에 대한 형식 규칙은 형식 안전성을 보장한다. 예를 들어, `Action
읽기 전용 데이터 타입(소스)은 공변적일 수 있고, 쓰기 전용 데이터 타입(싱크)은 반공변적일 수 있다. 소스와 싱크 역할을 모두 하는 변경 가능한 데이터 타입은 불변이어야 한다.
이러한 개념을 설명하기 위해 배열을 예로 들어보자. `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
```
C#에서는 컬렉션에 읽기 전용 방식으로 액세스하도록 하기 위해 배열 `object[]`를 전달하는 대신 인터페이스 `IEnumerable
4. 함수 타입에서
함수 타입에서 공변성과 반공변성은 함수 타입의 매개변수 타입과 반환 타입에 적용된다.
함수 ''f''가 ''g''보다 더 일반적인 유형의 인수를 받아들이고, ''g''보다 더 구체적인 유형을 반환하는 경우 ''g'' 대신 ''f'' 함수를 대체하는 것이 안전하다. 예를 들어, , , 타입의 함수는 이 예상되는 모든 곳에서 사용할 수 있다.
일반적인 규칙은 다음과 같다.
: if and .
추론 규칙 표기법을 사용하여 동일한 규칙을 다음과 같이 쓸 수 있다.
:
즉, 함수 타입 생성자는 ''매개변수(입력) 타입에 대해 반공변적''이며 ''반환(출력) 타입에 대해 공변적''이다. 이 규칙은 존 C. 레이놀즈에 의해 처음 공식적으로 명시되었고,[5] 루카 카르델리의 논문에서 더 널리 알려졌다.[6]
함수를 인수로 사용하는 함수를 다룰 때 이 규칙을 여러 번 적용할 수 있다. 예를 들어, 규칙을 두 번 적용하면 if 임을 알 수 있다. 다시 말해, 타입은 의 위치에서 ''공변적''이다.
함수 타입에서의 공변성과 반공변성은, 서브타입에서의 파라미터 타입과 리턴 타입의 일반화, 특수화를 제약하여 서브타이핑의 타입 안전성을 실현하기 위한 개념이 된다.
예를 들어 타입이 `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로 나타내면 다음과 같다.
객체지향 프로그래밍 언어들의 메소드 오버라이딩 규칙은 다음과 같다.
프로그래밍 언어 | 인자 타입 | 반환 타입 |
---|---|---|
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 | 공변 | 공변 |
공변 반환 형식을 허용하는 언어에서 파생 클래스는 메서드를 재정의하여 더 구체적인 유형을 반환할 수 있다.
자바의 예:
class CatShelter extends AnimalShelter {
Cat getAnimalForAdoption() {
return new Cat();
}
}
주류 객체 지향 프로그래밍 언어 중 자바, C++, C# (9.0 버전부터[7])은 공변 반환 형식을 지원한다. 공변 반환 형식 추가는 1998년 표준 위원회에서 승인된 C++ 언어의 첫 번째 수정 사항 중 하나였다.[8] 스칼라와 D 또한 공변 반환 형식을 지원한다.
역대 객체 지향 프로그래밍 언어에서의 메서드 상속의 공변성 반공변성은 다음과 같이 변천해 왔다. 에펠(1986년 발표)의 파라미터형은 공변이었지만, 리스코프 치환 원칙(1994년 발표)으로 반변으로 노선이 수정되었다.
5. 2. 반공변 인자 타입 (Contravariant method parameter type)
서브클래스가 슈퍼클래스의 메소드를 오버라이드할 때, 컴파일러는 메소드의 타입이 올바른지 확인해 타입 안전(type safe영어)을 보장해야 한다. 이때 오버라이드된 메소드는 더 일반적인 타입을 허용해야 한다. (인자 타입 반공변)객체지향 프로그래밍 언어들의 메소드 오버라이딩 규칙은 다음과 같다.
예를 들어 C#에서 `Action
마찬가지로, 재정의된 메서드가 기본 클래스의 메서드보다 더 일반적인 인수를 허용하는 것은 타입 안전하다.
```java
class CatShelter extends AnimalShelter {
void putAnimal(Object animal) {
// ...
}
}
```
이것을 실제로 허용하는 객체 지향 언어는 몇 개 없다 (예: mypy로 타입 검사를 수행할 때의 파이썬). C++, 자바 및 함수 오버로딩 및/또는 변수 섀도잉을 지원하는 대부분의 다른 언어는 이를 오버로드되거나 섀도잉된 이름을 가진 메서드로 해석한다.
그러나 세서는 공변성과 반공변성을 모두 지원했다. 재정의된 메서드의 호출 규칙은 ''out'' 매개변수 및 반환 값과 공변적이며, 일반 매개변수(모드 ''in'' 사용)와 반공변적이다.
역대 객체 지향 프로그래밍 언어에서의 메서드 상속의 공변성 반공변성은 다음과 같이 변천해 왔다. 에펠 (86년 발표)의 파라미터형은 공변이었지만, 리스코프 치환 원칙 (94년 발표)으로 반변으로 노선이 수정되었다.
5. 3. 공변 인자 타입 (Covariant method parameter type)
주류 프로그래밍 언어 중 에펠과 Dart[9]는 오버라이딩(overriding) 메서드의 매개변수가 슈퍼클래스의 메서드보다 '더' 구체적인 타입을 갖도록 허용한다(매개변수 타입 공변성). 따라서 다음과 같은 Dart 코드는 타입 검사를 통과하며, `putAnimal`은 기본 클래스의 메서드를 오버라이딩한다.```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
```
또는 C#에서는, 컬렉션에 읽기 전용 방식으로 액세스하도록 하기 위해 배열 object[]영어를 전달하는 대신 인터페이스 IEnumerable
7. 범주론과의 관계
이 용어들은 범주론의 공변 및 반공변 펀터 개념에서 유래되었다.[1] 객체를 자료형으로, 사상을 하위 형식 관계 ≤로 나타내는 범주 를 생각할 수 있다. (이는 부분 순서 집합을 범주로 간주하는 방법의 예시이다.) 함수형 자료형 생성자는 두 개의 자료형 ''p''와 ''r''을 받아 새로운 자료형 ''p'' → ''r''을 생성한다. 이는 의 객체를 의 객체로 만드는 것이다. 함수형 자료형에 대한 하위 형식 규칙에 따라 이 연산은 첫 번째 매개변수에 대해 ≤를 반전시키고 두 번째 매개변수에 대해 이를 유지하므로, 첫 번째 매개변수에서는 반공변 펀터이고 두 번째 매개변수에서는 공변 펀터이다.
서브타입 관계를 사상으로, 타입의 집합 ''C''를 범주로 볼 수 있다.[29] 프로그램에서 타입 ''p''의 값을 받아 타입 ''r''의 값을 반환하는 함수를 정의하면, 타입 시스템에서는 함수의 타입 「''p'' →''r'' 」을 생성한다. 이러한 함수의 타입 구문(타입 생성자)은 두 개의 타입으로부터 새로운 타입을 생성하는 사상 ''F'' : ''C'' ×''C'' → ''C''로 생각할 수 있다.
함수 타입의 규칙으로서 정적 타입 안전한[29] 규칙을 따르면, 이 사상 ''F''는 첫 번째 인수에 대해서는 서브타입 관계를 반전시켜 사상시키고(반변 함자에 해당), 두 번째 인수에 대해서는 서브타입 관계를 같은 형태로 사상시킨다(공변 함자에 해당).[1]
참조
[1]
문서
[2]
서적
C# in Depth
Manning
2019-03-23
[3]
웹사이트
Func
[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