ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 3.1 제네릭 함수 - 타입 인자 추론
    프로그래밍 언어 속 타입 2022. 5. 13. 00:20

     

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

     


     

    매개변수에 의한 다형성은 코드 중복을 크게 줄여 주는 대신 불편한 점이 있다. 꼬박꼬박 타입 인자를 써 줘야 한다는 점이다. 앞에서 그런 예시를 이미 보았다.

    choose<Int>(1, 2);
    choose<String>("Korean", "Foreigner");

    중요한 것은 1과 2 중에 하나를 고르는 것이지 1과 2의 타입이 Int인 것이 아니다. 함수를 정의할 때는 choose를 한 번만 정의하면 된다는 점이 좋았지만, 호출할 때는 이래서야 chooseInt와 chooseString을 각각 정의하고 호출하는 것과 별반 다르지 않다.

    이런 불편함을 해소하기 위해 매개변수에 의한 다형성을 제공하는 대부분의 언어는 타입 인자 추론을 함께 제공한다. 타입 인자 추론은 타입 추론의 일종으로, 제네릭 함수나 제네릭 메서드를 호출할 때 타입 인자를 개발자가 생략할 수 있도록 하는 기능이다. 타입 인자 추론이 지원되면 위 코드를 다음과 같이 작성할 수 있다.

    choose(1, 2);
    choose("Korean", "Foreigner");

    더 간결하면서도 코드를 이해하기는 여전히 쉽다. 제네릭 함수를 사용하는 곳이 얼마 없다면 타입 인자 추론의 유무가 별로 중요하지 않지만, 많이 사용한다면 있는 게 훨씬 편하다. 다음 예시만 봐도 그렇다.

    choose<Int>(choose<Int>(choose<Int>(1, 2), 3), 4);
    choose(choose(choose(1, 2), 3), 4);

    누가 봐도 두 번째 코드가 첫 번째 코드보다 작성하기도 편하고 읽기도 좋다.

    타입 인자 추론은 많은 경우에 타입 검사기에게 쉬운 일이다. choose를 예시로 생각해 보자. choose(1, 2)에서 1과 2의 타입은 Int이다. 그러므로 생략된 타입 인자는 Int이다. 또, choose("Korean", "Foreigner")에서는 "Korean"과 "Foreigner"의 타입이 String이므로 생략된 타입 인자가 String이다. 이처럼 보통은 인자의 타입을 확인하는 것만으로 타입 인자를 알아낼 수 있기에 타입 인자 추론이 성공한다.

    하지만 코드가 복잡해지다 보면 타입 검사기가 타입 인자 추론에 실패할 수 있다. 또는, 타입 인자 추론에는 성공했지만 내가 예상했던 것과 다른 타입이 타입 인자로 사용되어 프로그램의 다른 부분의 타입 검사를 실패하게 만들 수 있다. 그러니 타입 인자 추론이 언제나 내가 원하는 대로 되는 것은 아니라는 것을 항상 기억해야 한다. 타입 검사기가 내 프로그램을 거부한 이유를 잘 모르겠을 때는 생략한 타입 인자를 하나씩 다시 넣어 보는 것이 도움 될 수 있다.

     


    자바

    <T> T choose(T v1, T v2) { return ... ? v1 : v2; }
    int num = choose(1, 2);

    C++

    template <typename T> T choose(T v1, T v2) { return ... ? v1 : v2; }
    int num = choose(1, 2);

    C#

    T choose<T>(T v1, T v2) { return ... ? v1 : v2; }
    int num = choose(1, 2);

    타입스크립트

    function choose<T>(v1: T, v2: T): T { return ... ? v1 : v2; }
    let num: number = choose(1, 2);

    func choose[T any](v1 T, v2 T) T {
        if ... {
            return v1
        } else {
            return v2
        }
    }
    var num int = choose(1, 2)

    코틀린

    fun <T> choose(v1: T, v2: T): T = if (...) v1 else v2
    val num: Int = choose(1, 2)

    러스트

    fn choose<T>(v1: T, v2: T) -> T { if ... { v1 } else { v2 } }
    let num: i32 = choose(1, 2);

    스칼라

    def choose[T](v1: T, v2: T): T = if ... then v1 else v2
    val num: Int = choose(1, 2)
Designed by Tistory.