by ara.t.howard
[ Editors note: This quiz file can be downloaded:
Partial solutions are welcome. --JEG2 ]
# metakoans.rb is an arduous set of exercises designed to stretch
# meta-programming muscle. the focus is on a single method 'attribute' which
# behaves much like the built-in 'attr', but whose properties require delving
# deep into the depths of meta-ruby. usage of the 'attribute' method follows
# the general form of
#
# class C
# attribute 'a'
# end
#
# o = C::new
# o.a = 42 # setter - sets @a
# o.a # getter - gets @a
# o.a? # query - true if @a
#
# but reaches much farther than the standard 'attr' method as you will see
# shortly.
#
# your path, should you choose to follow it, is to write a single file
# 'knowledge.rb' implementing all functionality required by the koans below.
# as a student of meta-programming your course will be guided by a guru whose
# wisdom and pithy sayings will assist you on your journey.
#
# a successful student will eventually be able to do this
#
# harp:~ > ruby metakoans.rb knowledge.rb
# koan_1 has expanded your awareness
# koan_2 has expanded your awareness
# koan_3 has expanded your awareness
# koan_4 has expanded your awareness
# koan_5 has expanded your awareness
# koan_6 has expanded your awareness
# koan_7 has expanded your awareness
# koan_8 has expanded your awareness
# koan_9 has expanded your awareness
# mountains are again merely mountains
#
module MetaKoans
#
# 'attribute' must provide getter, setter, and query to instances
#
def koan_1
c = Class::new {
attribute 'a'
}
o = c::new
assert{ not o.a? }
assert{ o.a = 42 }
assert{ o.a == 42 }
assert{ o.a? }
end
#
# 'attribute' must provide getter, setter, and query to classes
#
def koan_2
c = Class::new {
class << self
attribute 'a'
end
}
assert{ not c.a? }
assert{ c.a = 42 }
assert{ c.a == 42 }
assert{ c.a? }
end
#
# 'attribute' must provide getter, setter, and query to modules at module
# level
#
def koan_3
m = Module::new {
class << self
attribute 'a'
end
}
assert{ not m.a? }
assert{ m.a = 42 }
assert{ m.a == 42 }
assert{ m.a? }
end
#
# 'attribute' must provide getter, setter, and query to modules which operate
# correctly when they are included by or extend objects
#
def koan_4
m = Module::new {
attribute 'a'
}
c = Class::new {
include m
extend m
}
o = c::new
assert{ not o.a? }
assert{ o.a = 42 }
assert{ o.a == 42 }
assert{ o.a? }
assert{ not c.a? }
assert{ c.a = 42 }
assert{ c.a == 42 }
assert{ c.a? }
end
#
# 'attribute' must provide getter, setter, and query to singleton objects
#
def koan_5
o = Object::new
class << o
attribute 'a'
end
assert{ not o.a? }
assert{ o.a = 42 }
assert{ o.a == 42 }
assert{ o.a? }
end
#
# 'attribute' must provide a method for providing a default value as hash
#
def koan_6
c = Class::new {
attribute 'a' => 42
}
o = c::new
assert{ o.a == 42 }
assert{ o.a? }
assert{ (o.a = nil) == nil }
assert{ not o.a? }
end
#
# 'attribute' must provide a method for providing a default value as block
# which is evaluated at instance level
#
def koan_7
c = Class::new {
attribute('a'){ fortytwo }
def fortytwo
42
end
}
o = c::new
assert{ o.a == 42 }
assert{ o.a? }
assert{ (o.a = nil) == nil }
assert{ not o.a? }
end
#
# 'attribute' must provide inheritance of default values at both class and
# instance levels
#
def koan_8
b = Class::new {
class << self
attribute 'a' => 42
attribute('b'){ a }
end
attribute 'a' => 42
attribute('b'){ a }
}
c = Class::new b
assert{ c.a == 42 }
assert{ c.a? }
assert{ (c.a = nil) == nil }
assert{ not c.a? }
o = c::new
assert{ o.a == 42 }
assert{ o.a? }
assert{ (o.a = nil) == nil }
assert{ not o.a? }
end
#
# into the void
#
def koan_9
b = Class::new {
class << self
attribute 'a' => 42
attribute('b'){ a }
end
include Module::new {
attribute 'a' => 42
attribute('b'){ a }
}
}
c = Class::new b
assert{ c.a == 42 }
assert{ c.a? }
assert{ c.a = 'forty-two' }
assert{ c.a == 'forty-two' }
assert{ b.a == 42 }
o = c::new
assert{ o.a == 42 }
assert{ o.a? }
assert{ (o.a = nil) == nil }
assert{ not o.a? }
end
def assert()
bool = yield
abort "assert{ #{ caller.first[%r/^.*(?=:)/] } } #=> #{ bool.inspect }" unless bool
end
end
class MetaStudent
def initialize knowledge
require knowledge
end
def ponder koan
begin
send koan
true
rescue => e
STDERR.puts %Q[#{ e.message } (#{ e.class })\n#{ e.backtrace.join 10.chr }]
false
end
end
end
class MetaGuru
require "singleton"
include Singleton
def enlighten student
student.extend MetaKoans
koans = student.methods.grep(%r/koan/).sort
attainment = nil
koans.each do |koan|
awakened = student.ponder koan
if awakened
puts "#{ koan } has expanded your awareness"
attainment = koan
else
puts "#{ koan } still requires meditation"
break
end
end
puts(
case attainment
when nil
"mountains are merely mountains"
when 'koan_1', 'koan_2'
"learn the rules so you know how to break them properly"
when 'koan_3', 'koan_4'
"remember that silence is sometimes the best answer"
when 'koan_5', 'koan_6'
"sleep is the best meditation"
when 'koan_7'
"when you lose, don't lose the lesson"
when 'koan_8'
"things are not what they appear to be: nor are they otherwise"
else
"mountains are again merely mountains"
end
)
end
def self::method_missing m, *a, &b
instance.send m, *a, &b
end
end
knowledge = ARGV.shift or abort "#{ $0 } knowledge.rb"
student = MetaStudent::new knowledge
MetaGuru.enlighten student
Quiz Summary
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:
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):
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):
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:
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
See Ryan Kinderman's write up on this quiz for a description of some problems he encountered.
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...