Metaprogramming in Ruby

Tags:

metakoans.rb

67 번째 루비퀴즈 문제인 metakoans 입니다. attr_accessor 와 유사하지만 좀 더 다양한 기능을 가진 메소드를 추가하는 문제입니다. 전 6번까지 풀고 항복해버렸네요…. 하지만 정말 많이 배울 수 있었습니다.

무책님의 홈페이지의 여기에 설명이 있습니다. 솔루션은 다음과 같습니다.

# knowledge.rb v3
class Module
   def attribute(x, &block)
    name, value = x.to_a[0]
    ivar = "@#{name}"
    define_method(name) do
       if instance_variables.include?(ivar)
        instance_variable_get(ivar)
       else
        value || (instance_eval &block if block)
       end
    end
    attr_writer name
    define_method("#{name}?") { !!send(name) }
  end
end

저는 제가 어려웠던 부분을 설명해보겠습니다. 이 문제를 푸는데 Module을 고치는데요.. 그 이유는 다음과 같습니다.

                         +------------------+
                         |                  |
           Object---->(Object)              |
            ^  ^        ^  ^                |
            |  |        |  |                |
            |  |  +-----+  +---------+      |
            |  |  |                  |      |
            |  +-----------+         |      |
            |     |        |         |      |
     +------+     |     Module--->(Module)  |
     |            |        ^         ^      |
OtherClass-->(OtherClass)  |         |      |
                           |         |      |
                         Class---->(Class)  |
                           ^                |
                           |                |
                           +----------------+

바로 모든 클래스의 상단엔 Object가 있고, Class는 Module을 상속합니다. 그리고 임의의 클래스는 Class라는 클래스의 인스턴스입니다. 그래서 koan7에서는 다음과 같은 식으로 클래스를 정의합니다.

c = Class::new {
  attribute('a'){ fortytwo }
  def fortytwo
    42
  end
}

o = c::new    

여기서 c는 새로운 class입니다. 즉, Class::new는 Class의 인스턴스인 또 다른 클래스를 생성합니다.

또, 코드 중에 보면

    define_method(name) do
       if instance_variables.include?(ivar)
        instance_variable_get(ivar)
       else
        value || (instance_eval &block if block)
       end
    end

와 같은 부분이 있습니다. 여기서 define_method는 “수신자”에게 인스턴스 메소드를 추가합니다. 따라서 c 에게 메소드가 추가되죠. 다음, do~end 사이를 보면 instance_variables, instance_variable_get, instance_eval 등에서 “인스턴스”란 c.new 로 생성된 인스턴스를 말합니다. 하지만 다음 부분은 정말 헷갈렸습니다.

value || (instance_eval &block if block)

사실 제가 이걸 못해서 7번을 못풀었는데요. 여기서 block을 부를때 반드시 instance_eval이 있어야합니다. 그냥 block.call을 하게되면 fortytwo를 못찾습니다. 그 이유는 define_method는 class_eval 과 마찬가지로 클래스 수준에서 동작합니다. 즉 define_method 내부는 c.new 로 생성된 인스턴스가 아니라 c 라는 클래스 그 자체입니다. 그렇지만 block 은 fortytwo를 부르고 있고, fortytwo는 c의 인스턴스 메소드입니다. 따라서 define_method 내에서 c의 인스턴스 메소드를 부를 수는 없죠.

따라서 koan7이 다음과 같은 식이었다면

c = Class::new {
  attribute('a'){ fortytwo }
  def self.fortytwo
    42
  end
end

솔루션의 답도

value || (block.call if block)

이라고 될 수 있습니다.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *