스칼라는 공변성(variance)와 반공변성(contravariance), 무공변성(invariance)을 지원한다.
이 세 가지 성질에 대해 위키백과 설명을 보면 이해하기 어렵지만, 정리해서 보면 쉽게 이해할 수 있다.
타입 T를 받았을 때,
[ +T ] 공변성 : 타입 T를 확장한 타입에 대해서 허용 [ T ] 무공변성 : 타입 T만 허용 [ -T ] 반공변성: 타입 T의 상위(부모) 타입에 대해서 허용 |
이 포스트에는 스칼라를 이용한 공변성 예제를 살펴보고, ECMAScript를 이용해 간단하게 만들어본 공변성-타입 제한 배열을 적어놓는다.
스칼라를 이용한 공변성 예제
스칼라는 언어 레벨에서 세 개의 성질을 지원한다.
/**
* 예시 클래스.
* People <- Student <- StudentPresident
*/
class People { }
class Student extends People { }
class StudentPresident extends Student { }
예시를 위한 클래스 선언
/**
* 공변성. 타입 T를 확장한 타입에 대해 허용한다.
*/
class Covariant[+T] {}
val people1 = new Covariant[People]
val student1 = new Covariant[Student]
val studentPresident1 = new Covariant[StudentPresident]
// 메소드 테스트
def say(user: Covariant[Student]) = println(user)
// say(people1) // Compile Error!
say(student1)
say(studentPresident1)
// 형 변환 테스트
val coPeople: Covariant[People] = new Covariant[StudentPresident]
// val coStudentPresident: Covariant[StudentPresident] = new Covariant[People] // Compile Error!
입력받은 타입 T를 확장한 타입을 허용하는 공변성
/**
* 반공변성. 타입 T의 상위(부모) 타입에 대해 허용한다.
*/
class Contravariant[-T] {}
val people2 = new Contravariant[People]
val student2 = new Contravariant[Student]
val studentPresident2 = new Contravariant[StudentPresident]
// 메소드 테스트
def say(user: Contravariant[Student]) = println(user)
say(people2)
say(student2)
// say(studentPresident2) // Compile Error!
// 형 변환 테스트
// val contraPeople: ContraVariant[People] = new ContraVariant[StudentPresident] // Compile Error!
val cotraStudentPresident: Contravariant[StudentPresident] = new Contravariant[People]
입력받은 타입 T의 상위 타입에 대해 허용하는 반공변성
/**
* 무공변성. 같은 타입만 허용한다.
*/
class Invariant[T] {}
val people3 = new Invariant[People]
val student3 = new Invariant[Student]
val studentPresident3 = new Invariant[StudentPresident]
// 메소드 테스트
def say(user: Invariant[Student]) = println(user)
// say(people3) // Compile Error!
say(student3)
// say(studentPresident3) // Compile Error!
// 형 변환 테스트
// val inPeople: Invariant[People] = new Invariant[StudentPresident] // Compile Error!
// val inStudentPresident: Invariant[StudentPresident] = new Invariant[People] // Compile Error!
입력받은 타입 T만 허용하는 무공변성 테스트
자바에서의 반공변성
자바에서는 공변성만을 지원한다. 제네릭과 함께 추가된 몇 가지 키워드가 있지만, 기존부터 LSP를 따르고 있었기에 별 차이가 없다.
// People <- Student <- StudentPresident
People people = new People();
Student student = new Student();
StudentPresident studentPresident = new StudentPresident();
// ?는 Student보다 큰(Student가 확장된) 클래스
List<? super Student> covarianceList = new ArrayList<>();
// covarianceList.add(people); // Compile Error
covarianceList.add(student);
covarianceList.add(studentPresident);
List<Student> covarianceList2 = new ArrayList<>();
// covarianceList2.add(people); // Compile Error
covarianceList2.add(student);
covarianceList2.add(studentPresident);
// true.
System.out.println(covarianceList.equals(covarianceList2));
공변성을 이용한 리스트.
뭔가 특별한걸 기대하지 말자.
ECMAScript로 만들어본 공변성 지원 배열
타입을 제한하고 공변성/반공변성/무공변성을 지원하는 배열을 간단하게 만들어 보았다.
기존 배열을 확장(extend)하지 않고 구성하도록 구현했고, 배열 메소드 중 값을 삽입하는 'push'와 'unshift' 메소드는 타입 체크 함수를 호출한 후 호출한다.
class TypedArray {
/**
* 타입을 제한하는 배열인 TypedArray 생성자.
* @param variance string 공변/반공변성. + 혹은 -, 공백으로 입력한다.
* @param type class 제한하고자 하는 타입.
*/
constructor(variance, type) {
const array = [];
/**
* 공변/반공변 값을 넣지 않은 경우
*/
if (!type) {
this.type = variance;
} else {
this.variance = variance;
this.type = type;
}
/**
* 값이 타입에 대해 반공변성을 가졌는지 체크하는 함수.
* @param value 추가하려는 값
* @param type 확인할 타입
* @returns {*}
*/
const superTypeCheck = (value, type) => {
if (!type) return false;
else if (value.constructor.name === type.name) return true;
else return superTypeCheck(value, type.__proto__);
};
/**
* 값이 선언된 TypedArray 객체에서 허용하는 타입인지 검사하는 함수.
* @param value 추가하려는 값
* @returns {*} 추가할 수 있는지 여부
*/
const typeCheck = (value) => {
const typeOfValue = typeof(value);
const thisType = this.type.name;
switch (thisType) {
// primitive type
case 'number' :
case 'symbol' :
case 'string' :
return typeOfValue === thisType.toLowerCase();
default :
if (this.variance === '+' && value instanceof this.type) return true;
else if (this.variance === '-') return superTypeCheck(value, this.type);
else if (value.constructor.name === thisType) return true;
else return false;
}
};
/**
* 배열의 프로토타입을 복사한다.
* 배열에 값을 추가하는 push, unshift 함수는 타입을 체크한다.
*/
for (let field of Object.getOwnPropertyNames(Array.prototype)) {
this[field] = (value) => {
if (field === 'push' || field === 'unshift') {
if (!typeCheck(value)) throw 'IllegalTypeException';
}
return array[field].call(array, value);
};
}
/**
* 리스트를 반환하는 메소드
* @returns {Array} 복사된 원본 리스트
*/
this['get'] = () => array.slice();
}
}
배열 클래스 선언
/**
* 예시 클래스
* People <- Student <- StudentPresident
*/
class People {
constructor(name) {
this.name = name;
}
}
class Student extends People { }
class StudentPresident extends Student { }
// 객체 생성
const people = new People('Jack');
const student = new Student('john');
const studentPresident = new StudentPresident('quill');
예시 클래스 및 배열 객체 생성
// 공변성 배열 테스트
const varianceArr = new TypedArray('+', Student);
// varianceArr.push(people); // Exception!
varianceArr.push(student);
varianceArr.push(studentPresident);
// 무공변성 배열 테스트
const invarianceArr = new TypedArray(Student);
invarianceArr.push(people); // Exception!
invarianceArr.push(student);
invarianceArr.push(studentPresident); // Exception!
// 반공변성 배열 테스트
const contravarianceArr = new TypedArray('-', Student);
contravarianceArr.push(people);
contravarianceArr.push(student);
// contravarianceArr.push(studentPresident); // Exception!
테스트.
'ETC' 카테고리의 다른 글
책임과 기술, 그리고 오버엔지니어링 (0) | 2022.01.26 |
---|---|
알아두면 좋은 간단한 인프라 상식 (0) | 2022.01.11 |
개발하는 직장인 (0) | 2021.11.30 |
Angular, Nginx 프록시 설정 (0) | 2018.02.26 |
Data URI, 파일을 문서에 임베드하기 (0) | 2018.02.22 |