Ruby 언어의 버그?

sugarlessgirl의 이미지

제목이 스포츠신문틱하군요.. -_-;;;

Ruby 언어에서 linked library 된 object 를 상속한 경우 생성자인 initialize 메소드가 오버라이드가 안되는 사실을 발견하였습니다. -_-

require 'wxruby'

class VBoxSizer < Wx::BoxSizer
 def initialize
  super Wx::VERTICAL
 end
end

생략 

VBoxSizer.new

생성자의 argument 가 맞지 않다고 에러납니다.
상속받는 클래스가 linked library 된 object 가 아니라 순수 Ruby 로 작성된 object 이면 문제가 없습니다.

구글에서 검색한 결과 저와 같은 경우를 2건 발견하였으나
하나는 일본어라 소용없고-_- 네이버 일한번역으로 번역해서 본 결과 별다른 영양가가 없는 결론인 듯 하였고,

다른 하나는
http://www.talkaboutprogramming.com/group/comp.lang.ruby/messages/117546.html
이것인데 답변이 없습니다. -_-

좋은언어라고 느꼈었는데.. 김빠지게하네요 ㅠ_ㅠ

nohmad의 이미지

linked library란 게 extension library를 말씀하시는 거라면, 말씀하신 버그는 ruby extension 일반에 해당되는 버그가 아니라, 해당 라이브러리의 버그입니다. 해당 라이브러리 제작자에게 문의하셔야지요. wxruby의 프로젝트 싸이트에 가셔서 공식적으로 버그리포트하시면 됩니다.

http://rubyforge.org/tracker/?atid=218&group_id=35&func=browse

버그리포트한다고 해서 당장 해결될 거라고 기대하기는 힘들지만, 쓰는 사람도 많지 않은 오픈소스 프로젝트에 대해 전혀 상관도 없는 곳에서 불평하는 것은, 만일 그 프로젝트의 개발자가 이 쓰레드를 보고 있다면 무척이나 상심하게 만들 수 있는 일입니다.

이노메이커의 이미지

냉무.

sugarlessgirl의 이미지

혹시라도 나중에 이글을 보고 오해하실 분이 생길까봐 답글을 답니다. -_-;

큰 문제는 아니고 ruby 가 method overloading 을 지원안해서 생기는 문제였습니다.
(BoxSizer 의 initialize 메소드는 argument 를 하나 받습니다.)

http://webdev.jvoegele.com/software/langcomp.html
(오버로딩은 당연히 지원되는 건줄 알았는데 그게 아니군요 smalltalk 도 지원을 안하네요-_-)

Ruby 에서 오버라이딩 전혀 문제없고, wxruby 지금까지 문제점을 단 한번 경험한것 빼고는 잘 돌아갑니다. (이것도 제가 실수한 것인지도 모릅니다.)

PS.
이곳이 쓰는 사람도 많지 않은 오픈소스 프로젝트에 대해 전혀 상관도 없는 곳처럼 여겨지지는 않아서 여기다 올렸습니다만.. (자유게시판에다가 올린것은 잘못했다고 생각합니다. -_-)
앞으로는 주의하겠습니다.

nohmad의 이미지

Method Overloading은 정적 타입 선언을 갖지 않는 언어(Ruby, Smalltalk, Python 등)에서는 대부분 지원하지 않는 기능입니다. 언어 차원에서 메쏘드 오버로딩을 지원하기 위해서는, 런타임 또는 컴파일타임에 메쏘드의 리턴 타입과 아규먼트들의 프로토타입을 가지고 동일한 이름의 메쏘드들 중에서 어떤 메쏘드를 호출해야 할지 결정할 수 있어야 합니다. 정적 타입 언어에서는 보통 컴파일타임에 기본적인 타입 체크를 수행하면서 오버로드된 메쏘드들의 이름을 컴파일러가 인식할 수 있는 다른 이름으로 재정의(네임 맹글링)하는 방식으로 구현됩니다. 하지만 동적 타입 언어에서는 이러한 효율적인 심볼 재배치 과정 없이 런타임에 이와 비슷한 작업이 수행되어야 하는데, 그러려면 매연산마다 타입 체크를 해야 하고 이것은 곧 엄청난 성능 저하로 이어집니다. 물론 동적 타입 언어라도 컴파일을 지원한다면 타입 추론을 통해 성능 저하 없이 메쏘드 오버로딩을 지원하는 것이 가능할 것입니다.

말씀하신 상황은 메쏘드 오버로딩 보다는, Ruby 언어에서 super의 구현상의 문제인 것으로 보입니다. super를 호출할 때 메쏘드 이름과 아규먼트 사이에 공백 없이 괄호를 넣어서 다시 시도해보십시요. 괄호 없이 메쏘드 호출이 가능하도록 한 것 때문에, super가 예상대로 동작하지 않은 것 같습니다.

super(WX::VERTICAL)

super에 대해 조사(inspection)를 좀 해보니 단순한 메쏘드나 클래스 오브젝트가 아니라 Binding을 통한 트릭 같은데, 아규먼트 없이 super를 호출하면 기본으로 서브클래스의 메쏘드와 동일한 아규먼트가 전달된다는 규칙은 확실히 조금 까다로운 것 같군요.

그외 확장 라이브러리의 경우 그 구현자에 따라 예상한 것과 다르게 동작할 수가 있는데, 그것은 Ruby 자체의 문제가 아니라 확장 라이브러리의 문제인 경우가 많습니다. 간단하게 C 확장 라이브러리를 만들어서 테스트해보았는데, C 확장 클래스라고 해서 생성자 오버라이딩이 안되는 것은 아닙니다. super 호출도 물론 정상적으로 잘 되구요.

require 'counter'

class DerivedCounter < Counter
  def initialize(initval)
    super()
    initval.times { increment }
  end
end

c = Counter.new
puts c.count
c.increment
puts c.count

d = DerivedCounter.new(5)
puts d.count
d.increment
puts d.count

위에서 require한 counter는 C로 구현한 확장 라이브러리이고, Ruby 확장 라이브러리 규칙에 따라 실제 파일명은 counter.so입니다. 아래와 같은 가상의 Ruby 클래스와 똑같이 작동하도록 만들어졌습니다. 보시다시피 수퍼클래스와 서브클래스의 생성자가 받는 아규먼트 개수가 다르다는 걸 알 수 있습니다.

class Counter
  attr_reader :count
  def initialize
    @count = 0
  end
  def increment
    @count += 1
  end
end

실제 C 소스는 Minero Aoki씨의 소스를 가져다 썼습니다. 소스 설명이나 빌드 방법은 생략하겠습니다.

/*
 * http://i.loveruby.net/w/RubyExtensionProgrammingGuide.html
 */

#include "ruby.h"

struct counter {
    VALUE count;
};

static void
counter_mark(struct counter *ptr)
{
    rb_gc_mark(ptr->count);
}

static VALUE
counter_alloc(VALUE klass)
{
    struct counter *ptr = ALLOC(struct counter);
    return Data_Wrap_Struct(klass, counter_mark, -1, ptr);
}

static VALUE
counter_initialize(VALUE self)
{
    struct counter *ptr;
    Data_Get_Struct(self, struct counter, ptr);
    ptr->count = 0;
    return Qnil;
}

static VALUE
counter_count(VALUE self)
{
    struct counter *ptr;
    Data_Get_Struct(self, struct counter, ptr);
    return INT2FIX(ptr->count);
}

static VALUE
counter_increment(VALUE self)
{
    struct counter *ptr;
    Data_Get_Struct(self, struct counter, ptr);
    ptr->count++;
    return self;
}

void
Init_counter(void)
{
    VALUE Counter;
    Counter = rb_define_class("Counter", rb_cObject);
    rb_define_alloc_func(Counter, counter_alloc);
    rb_define_private_method(Counter, "initialize",
                             counter_initialize, 0);

    rb_define_method(Counter, "increment", counter_increment, 0);
    rb_define_method(Counter, "count", counter_count, 0);
}