Ruby double dispatch is flawed?

Tags:

타입이 없이는 우아한 double dispatch는 불가능한 걸까요….

다음은 루비에서 정수와 새로 정의한 Roman 숫자간의 덧셈을 간략히 구현한 것입니다.

class Roman
    def initialize(val)
        @value = val
    end

    def coerce(other)
        if Integer === other
            [other, @value]
        else
            [Float(other), Float(@value)]
        end
    end
end

class IntSim
    def initialize(val)
        @value = val
    end

    def +(other)
        v1, v2 = other.coerce(@value)
        v1 + v2
    end
end


int_3 = IntSim.new(3)
vi = Roman.new(6)

# Shows you how to compute 3 + vi.
puts int_3 + vi

루비에서는 2개 숫자간의 덧셈시 int+int는 int, float+int는 float, float+float은 float같은 타입 conversion을 위해 coerce를 씁니다. 예를들어, 앞서의 예처럼 3+vi 를 호출하게 되면 3이 vi.coerce(3)을 호출합니다. 그러면 vi의 클래스인 Roman 쪽에서는 넘어온 3과 자기 자신의 타입을 바탕으로 하여 두개 숫자를 conversion합니다. 그러면 3의 operator+는 이 변환된 타입을 가지고 최종 결과를 계산합니다. (그래서 double dispatch라고 불립니다. 2번 호출하니까.)

문제는 이 예제를 본 Programming Ruby책이 잘못된 건지, 루비의 한계인건지 사실상 이와같은 double dispatch가 아무런 의미도 없다는 것입니다. double dispatch는 이러한 예와같이 두개의 hierarchy가 존재할 때, 양쪽 hierarchy에 따라서 적절히 메소드를 호출하기 위함입니다. 그리고 그 구현은 RTTI(i.e., reflection)과 if-else 사다리를 쓰는 방법, A의 a라는 메소드를 부르면 a는 B의 b를 부르는 방법이 있습니다. 물론 후자가 더 나은 방법이죠.

두번째 방법에서는, A의 a를 부르면, a라는 메소드에서는 자신의 type이 결정납니다. 따라서 B의 b를 부를때는 자신의 타입정보를 b에게 넘깁니다. b는 호출되자마자 B hierarhcy 측에서의 자신의 타입을 알게되면, 결국 모든 타입정보를 알게 된 셈이니까 양쪽의 타입에 근간한 dispatch가 가능한 것이죠.

하지만 앞서의 예에서는 IntSim에서 Roman 을 부르지만, Roman쪽에 “나는 int거든”이란 정보를 넘기지 않으니 다시 Roman에서는 if-else 사다리를 타고 있군요.. 이건 말이 안되는거 아닌지.

귀찮아서 대강 적절히 C++이라면 어떨까를 짜봤습니다.

class Roman
{
private:
    int val;

public:
    Roman(int val):val(val) { }
    int operator+(const int &i) const
    {
        return val + i;
    }

    float operator+(const float &f) const
    {
        return val + f;
    }
};


class Numeric { };

class IntSim: public Numeric
{
private:
    int val;

public:
    IntSim(int val):val(val) { }
    int operator+(const Roman &o) const
    {
        return o.operator+(val);
    }
};

class FloatSim: public Numeric
{
private:
    float val;

public:
    FloatSim(float val):val(val) { }
    float operator+(const Roman &o) const
    {
        return o.operator+(val);
    }
};

제가 생각할 땐 Programming Ruby 책이 잘못된 것 같군요.. 적어도 루비같은 duck typing언어에서는 아예 double dispatch를 포기하던가, 아니면 확실히 IntSim에서 Roman으로 갈때 Int측의 정보를 줄 수 있게 보조 장치를 만들어야 한다는 생각이 듭니다. 사실 Double Dispatch의 가장 좋은 솔루션은 Scott Myers 왈, “디자인을 새로해라” 잖아요.