ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 3.2 제네릭 타입 - 제네릭 클래스
    프로그래밍 언어 속 타입 2022. 5. 13. 00:45

     

    이 글은 인사이트 출판사의 제안으로 작성 중인 책 『프로그래밍 언어 속 타입』 원고의 일부입니다.

     


     

    개발자가 자신만의 제네릭 타입을 직접 정의하고 싶은 경우도 종종 있다. 이런 이유로 대부분의 언어는 새로운 제네릭 타입을 정의할 수 있는 기능도 제공한다. 대표적인 기능이 제네릭 클래스generic class다.

    제네릭 클래스는 타입 매개변수를 가진 클래스다. 정의할 때는 제네릭 함수와 비슷하게 타입매개변수를 명시해야 하고, 사용할 때는 제네릭 타입으로서 리스트나 맵과 비슷한 방식으로 사용된다. 이미 살펴본 개념들과 비슷하기에 별로 어려울 게 없다.

    앞서 정의했던 choose 함수를 제네릭 클래스의 메서드로 만들어 보자. 목표는 사용자에게 같은 질문을 여러 번 물어 볼 때의 코드 중복을 최소화하는 것이다. choose<Int>(1, 2)는 사용자가 1과 2 중 하나를 선택하도록 한다. 만약 1과 2 중에 골라야 하는 상황이 여러 번 있다면 매번 choose<Int>(1, 2)라고 작성해야 한다. 객체를 사용함으로써 1, 2를 코드에 한 번만 쓰도록 할 수 있다. 1과 2를 가지고 있는 Chooser(1, 2)라는 객체를 만들어 c라는 변수에 넣어 놓고, 1과 2 중 골라야 할 때마다 c.choose()를 사용하는 것이다.

    Chooser 클래스를 다음과 같이 제네릭 클래스로 정의할 수 있다. choose는 제네릭 메서드가 아님에 유의하자.

    class Chooser<T> {
        T v1;
        T v2;
    
        T choose() {
            print(this.v1); print(this.v2); print(...);
            Int input = readInt();
            return (input == 0) ? this.v1 : this.v2;
        }
    }

    Chooser는 타입 매개변수 T를 가진다. 타입 매개변수 T를 클래스를 정의하는 동안 사용할 수 있다. 변수와 메서드의 타입 등에 T를 사용할 수 있는 것이다. 위 코드에서는 각 Chooser 객체가 가지고 있는 값에 해당하는 필드인 v1과 v2의 타입이 T이다. 또한, v1과 v2의 값 중 하나를 반환하는 메서드 choose의 타입 역시 T이다.

    Chooser가 제네릭 클래스이므로 그냥 Chooser 타입의 값이란 것은 없다. Chooser<Int>나 Chooser<String> 타입의 값이 있을 뿐이다. Chooser<Int> 객체의 필드 v1과 v2의 타입은 Int이며, choose 메서드의 결과 타입은 Int이다. 따라서 다음과 같이 코드를 작성할 수 있다.

    Chooser<Int> c = Chooser<Int>(1, 2);
    Int v = c.choose();

    같은 방식으로 Chooser<String> 객체의 필드 v1과 v2의 타입 및 choose의 결과 타입은 String이 된다.

    Chooser<String> c = Chooser<String>("Korean", "Foreigner");
    String v = c.choose();

     

    타입 인자 추론이 지원된다면 같은 코드를 약간 더 간결하게 적을 수 있다.

    Chooser<Int> c = Chooser(1, 2);
    Int v = c.choose();
    Chooser<String> c = Chooser("Korean", "Foreigner");
    String v = c.choose();

     


    자바

    class Chooser<T> {
        T v1;
        T v2;
        T choose() { return ... ? this.v1 : this.v2; }
    }
    
    Chooser<Integer> c = new Chooser<Integer>();
    int n = c.choose();

    C++

    template <typename T> class Chooser {
    public:
        T v1;
        T v2;
        T choose() { return ... ? v1 : v2; }
    };
    
    Chooser<int> *c = new Chooser<int>();
    int n = c->choose();

    C#

    class Chooser<T> {
        T v1;
        T v2;
        public T choose() { return ... ? this.v1 : this.v2; }
    }
    
    Chooser<int> c = new Chooser<int>();
    int n = c.choose();

    타입스크립트

    class Chooser<T> {
        v1: T;
        v2: T;
        choose(): T { return ... ? this.v1 : this.v2; }
    }
    
    let c: Chooser<number> = new Chooser<number>();
    let n: number = c.choose();

    type Chooser[T any] struct {
        v1 T
        v2 T
    }
    func (c Chooser[T]) choose() T {
        if ... {
            return c.v1
        } else {
            return c.v2
        }
    }
    
    var c Chooser[int] = Chooser[int]{1, 2}
    var n int = c.choose()

    클래스 대신 구조체가 있으므로 Chooser를 제네릭 구조체로 정의했다.

    코틀린

    class Chooser<T>(val v1: T, val v2: T) {
        fun choose(): T = if (...) this.v1 else this.v2
    }
    
    val c: Chooser<Int> = Chooser<Int>(1, 2)
    val n: Int = c.choose()

    러스트

    struct Chooser<T> { v1: T, v2: T }
    impl<T> Chooser<T> {
        fn choose(self) -> T {
            if ... { self.v1 } else { self.v2 }
        }
    }
    
    let c: Chooser<i32> = Chooser::<i32> { v1: 1, v2: 2 };
    let n: i32 = c.choose();

    클래스 대신 구조체가 있으므로 Chooser를 제네릭 구조체로 정의했다.

    스칼라

    class Chooser[T](v1: T, v2: T):
      def choose(): T = if ... then v1 else v2
    
    val c: Chooser[Int] = Chooser[Int](1, 2)
    val n: Int = c.choose()

    하스켈

    data Chooser a = Chooser a a
    choose (Chooser v1 v2) = if ... then v1 else v2
    
    c :: Chooser Int
    c = Chooser 1 2
    
    n :: Int
    n = choose c

    클래스 대신 대수적 데이터 타입algebraic data type이 있으므로 Chooser를 제네릭 타입으로 정의한 뒤 choose를 제네릭 함수로 정의했다. 첫 줄에서 = 앞의 Chooser a의 a는 타입 매개변수 a를 선언한 것이고, = 뒤의 Chooser a a는 타입 매개변수 a를 사용하여 Chooser a 타입의 값이 두 개의 a 타입 값으로 구성됨을 표현한 것이다.

    오캐멀

    type 'a chooser = Chooser of 'a * 'a
    let choose (Chooser (v1, v2)) = if ... then v1 else v2
    
    let c: int chooser = Chooser (1, 2)
    let n: int = choose c

    클래스 대신 대수적 데이터 타입이 있으므로 chooser를 제네릭 타입으로 정의한 뒤 choose를 제네릭 함수로 정의했다. 타입 매개변수의 이름은 '로 시작해야 한다. 첫 줄에서 = 앞의 'a chooser의 'a는 타입 매개변수 'a를 선언한 것이고, = 뒤의 Chooser of 'a * 'a는 타입 매개변수 'a를 사용하여 'a chooser 타입의 값이 두 개의 'a 타입 값으로 구성됨을 표현한 것이다.

Designed by Tistory.