Forum: Ruby metakoans.rb (#67)

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
4299e35bacef054df40583da2d51edea?d=identicon&s=25 James Gray (bbazzarrakk)
on 2006-02-23 14:55
(Received via mailing list)
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...
This topic is locked and can not be replied to.