From: Ruby Quiz Date: 2006-02-23T22:53:15+09:00 Subject: [SUMMARY] metakoans.rb (#67) This was a wildly popular quiz with solutions all over the board. I will do my best to hit the highlights below... Where to Define Probably the first question that needs answering when meta-programming is, where do I stick this method? Some defined it at the top-level (creating a private method on Object), others reopened Object to stick it inside, and a third group defined a method in Module. All three would satisfy the tests. The real question is, how do we know which one is best? I used the extremely unscientific method of copy Ruby. The core method attr() is defined in Module, so it seemed like attribute() belonged there too. Ross Bamford had much more intelligent reasoning. Here is Ross's response to Adam Shelly's question about why people were using Module when Object works: Well, you're extending Object with a method that relies on methods defined in Module (class_eval), and your method is available in places where it really didn't ought to be. [snip quoted solution] attribute :oops -:7:in `attribute': undefined method `class_eval' for main:Object (NoMethodError) from -:3:in `attribute' from -:15 By defining an instance method on Module (from which Class inherits) you ensure that a) class_eval is there, and b) attribute can be called only in a module/class definition context. That convinced me. Now that we know where it goes, let's break down a solution. A Common Approach Here's a solution, similar to many submitted, by Christian Neukirchen: class Module def attribute(a, &block) if a.kind_of? Hash a, default = a.to_a.first else default = nil end a = a.to_sym ivar = "@#{a}" define_method(a) { if instance_variables.include? ivar instance_variable_get ivar else block ? instance_eval(&block) : default end } define_method("#{a}=") { |v| instance_variable_set ivar, v } define_method("#{a}?") { !!__send__(a) } end end The if statement at the beginning of the method handles the case where a default argument is, or isn't, provided. If the first argument to attribute() was a Hash, a default was given and it is extracted here. Note that this solution just pulls a single pair from the Hash. That's all the tests required, but many chose to support declaring multiple arguments at once as we will see in a bit. If a default was not provided, it is set to nil. The next two lines place the attribute method and instance variable names in local variables for future use. The last chunk of code defines the three methods attribute() is expected to create. Note that they are built using define_method(), which takes the name of the method to define and a block of code to execute as the method body. Now remember, Ruby's blocks are closures and thus have access to the local variables defined in the scope where they are created. That's the key to this kind of solution, as the code needs to access things like the default argument and/or the provided block. The first method, the attribute getter, checks to see if the instance_variables() of the class include?() our new attribute. In other words, this is a check to see if the variable has been set yet. If it has, the stored value is retrieved and returned with a call to instance_variable_get(). If the variable is not set, the code executes a block, if provided, or uses the default (which may have been set to nil earlier). The tests required that the block have access to the instance though, so the block is instance_eval()ed when invoked. The second method, the setter, is trivial. Just stuff the new value in the variable with instance_variable_set(). The third and final method, for querying, uses a clever trick. Technically, the tests only required that the query method return the stored value and some implemented it that way. Here's Florian Gross's implementation (name holds the attribute's name): alias_method(:"#{name}?", name) The real purpose of the method though is to find out if the attribute currently holds a true or false value. Christian made sure only this true/false answer was returned. The code works by fetching the value with __send__() (calling the getter) and hitting it with Ruby's ! operator. That will convert the value to it's opposite, but the key to the conversion is that it will only return true or false. We really don't want the opposite though, so the ! operator is applied again. That takes us back to the original truth of the argument, but now in true/false form. Using def Instead of define_method() You can solve this challenge by defining methods normally, but you have to approach it differently. First, def is a keyword and not a method in Ruby. This has two effects: you can't just inline the definitions and the method bodies are not closures. Neither of these is a deal-breaker, they just require an additional tool. Here's a solution introducing class_eval(), by Wilson Bilkovich (minus the clever poems that made it exactly 42 lines): class Module def attribute(name, &block) return name.map {|k,v| attribute(k) {v}} if name.is_a?(Hash) define_method("__#{name}__", block || proc{nil}) class_eval <<-ZEN attr_writer :#{name} def #{name} defined?(@#{name}) ? @#{name} : @#{name} = __#{name}__ end def #{name}? true unless #{name}.nil? end ZEN end end Here you can see support for multiple attributes right off the bat. Notice that it works just by recursively calling attribute(), passing a block that returns the default argument. That makes it so you only have one special case, the block. I like the simplification there. Next you can see that this code uses define_method() to set a __attribute_name__() method, which will return the default value for that attribute. It uses the block as a method body, if one was provided. This eliminates the need for instance_eval() we saw in the last solution, since the method body naturally works on the object instance. In the event we don't have a block for the body, one is manufactured to return a default nil. Now we get to defining the three methods. Because we are using def here and have lost the closure trick, a new trick is needed to pass down the attribute name. Wilson wraps the definitions in class_eval(), which just executes the code in the context of the class. The String argument version of class_eval() is used here (with a heredoc for the String) so that the attribute name can be interpolated as needed. This time the setter is generated with attr_writer(), which should be familiar enough to everyone I hope. The getter uses some new techniques though. First, it checks for the instance variable using Ruby's defined? keyword. This should be faster for classes that have a bunch of instance variables because the Array doesn't need to be constructed and traversed. Just as with the first solution, the variable is returned if set. If it's not, the default method created earlier is invoked to set the variable (efficient, since it will only happen once) and that value is returned. We will skip the query method this time, since I don't believe it functions as Ara intended. Some people, including myself, misunderstood the intentions of this method. No big deal. The Inner Meaning Approach This week I am going to reverse my normal strategy of suggesting everyone read all the solutions and recommend that people spend some time studying the quiz itself. It's really quite clever if you stop and think about it. Ara couldn't use Test::Unit in this case, because the koans needed to be tried in a set order and the process halted as soon as one failed. Given that Ara rolled up a linear fail-fast test framework just for the purposes of this quiz. Well that and you simply have to respect a framework with lines like: MetaGuru.enlighten student Once you have studied the quiz a bit, one of the solutions will take on a whole new meaning for you. Ilmari Heikkinen sent in a solution that passes quiz tests not by properly implementing the attribute() method, but instead by altering parts of the quiz framework. When you think about it, this kind of solution is also a form of meta-programming and thus was probably equally enlightening. Ilmari's code is actually eight solutions in one and it randomly selects which one it will bypass the tests with when run. Even more fun, these solutions come with great sayings in the spirit of the quiz itself. Here are a few of my favorite hacks used by Ilmari: # ... puts "sometimes thinking about a problem makes it worse" class MetaStudent; def ponder(a) 0 end end # ... puts "don't send a student to do a guru's job" class MetaStudent; def send(a) 0 end end # ... puts "question what you have been taught" module MetaKoans; 9.times{|i| eval("def koan_#{i+1};0 end")} end # ... The first one replaces the method the MetaStudent uses to ponder() each koan with a trivial implementation. The second works in a similar way replacing send(), which is used to call the koans themselves, with a trivial implementation. The third redefines the problems themselves with trivial implementations. Don't miss the other five! Wrap Up My thanks to the quiz creator and numerous solvers for what turned out to be a fun little diversion. We must do more problems like this! Tomorrows problem is the first from Caleb Tennis, after I killed two previous suggestions. (Yes, I'm very, very mean.) Luckily Caleb didn't give up on me and this problem is a great one...