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)
이라고 될 수 있습니다.