The three rules of Ruby Quiz:
1. Please do not post any solutions or spoiler discussion for this quiz until
48 hours have passed from the time on this message.
2. Support Ruby Quiz by submitting ideas as often as you can:
http://www.rubyquiz.com/
3. Enjoy!
Suggestion: A [QUIZ] in the subject of emails about the problem helps everyone
on Ruby Talk follow the discussion.
···
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
by ara.t.howard
[ Editors note: This quiz file can be downloaded:
http://rubyquiz.com/metakoans.rb
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
Wow... I feel somewhat enlighted just reading the quiz...
Beautiful. I have aggravating work to do today, and a billion "side-projects" to distract me, and just when I thought that the chance of me getting any real work done couldn't get any lower, you have to come along and drop a pre-built set of tests for a metaprogramming exercise. Hrm, metaprogramming Ruby, or writing aggravating and useless C++ and VB? I am so very screwed.
A most excellent quiz.
I am pretty eager to check out the solutions people come up with, since I get a feel you can make it work with a vast array of different approaches. I just took the solution that I, as a newbie, found first. I am looking forward to see if there are some gurus who can provide a more succinct solution than the one I came up with.
If I have a complaint about this quiz it's that I solved some of the parts without even understanding them fully. My solution ended up from needing a fix on koan 1, 6 and 7 only...
/Christoffer
<pedantic>
What if they provide both?
</pedantic>
pth
···
On 2/17/06, Ruby Quiz <james@grayproductions.net> wrote:
#
# 'attribute' must provide a method for providing a default value as hash
#
#
# 'attribute' must provide a method for providing a default value as block
# which is evaluated at instance level
#
My solution is attached. Actually, two different styles of the same solution. Neither one is anywhere near 13 lines -- I'll be very interested to see the work of people who actually know this language.
A couple of subtleties. (1) The first time the attribute is set, I redefine the setter and getter to just be ivar accessors. (2) I only ever evaluate the block once: the initial version of the getter calls the setter with the result of evaluating the block.
And I'll echo everyone else: excellent quiz.
Luke Blanshard
knowledge.rb (1.98 KB)
Ruby Quiz wrote:
# 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.
Here we go then.
I have a golfed (8 lines) and a regular solution (21 lines) and generally do the simplest thing that makes the tests work.
Got it to run in two tries by the way. I failed the fourtytwo test at first. After that everything worked fine. 
And thanks for a great quiz. Being able to do things test first rules!
flgr-metakoans-golfed.rb (407 Bytes)
flgr-metakoans.rb (514 Bytes)
···
--
http://flgr.0x42.net/
Hi,
It's 18 lines when you strip comments. The only thing with it is that
the #{sym}? query method doesn't behave exactly right (returning true or
false) but it makes it a bit quicker.
Also I wished for instance_exec to allow the symbol to be passed to the
default block...
Thanks, Ara, for a cool quiz - I had more fun reading the quiz code than
writing my solution 
class Module
# call-seq:
# attribute :a -> true
# attribute :a, :c => 45, :d => 'stuff' -> true
# attribute(:a) { || default } -> true
# attribute(:a, :b, :c => 4) { || default a, b } -> true
def attribute(*args, &blk)
args.inject({}) { |hsh,arg|
(arg.respond_to?(:to_hash) ? hsh.merge!(arg) : hsh[arg] = nil) ; hsh
}.each { |sym, default|
ivar = :"@#{sym}"
define_method(sym) do
if instance_variables.include? ivar.to_s
instance_variable_get(ivar)
else
instance_variable_set(ivar, default || (instance_eval &blk if blk))
# Ruby 1.9: (instance_exec(sym, &blk) if blk))
end
end
# define_method("#{sym}?") { instance_variable_get(ivar) ? true : false }
alias_method "#{sym}?", sym
attr_writer sym
}.any?
end
end
···
--
Ross Bamford - rosco@roscopeco.REMOVE.co.uk
Here's mine, it's 8 one-liners of which one is randomly picked:
[
lambda{
puts "humbleness is a virtue"
def attribute(*a) end; module MetaKoans; def assert; 0 end end
},lambda{
puts "sometimes thinking about a problem makes it worse"
class MetaStudent; def ponder(a) 0 end end
},lambda{
puts "finish what you started"
def method_missing(*a) end; def abort(a) 0 end
},lambda{
puts "don't overextend yourself"
public; def extend(m) def koan_0; 0 end end
},lambda{
puts "know thyself";$-w=nil
class Array;def grep(a) [:id] end end
},lambda{
puts "don't send a student to do a guru's job"
class MetaStudent; def send(a) 0 end end
},lambda{
puts "question what you have been taught"
module MetaKoans; 9.times{|i| eval("def koan_#{i+1};0 end")} end
},lambda{
puts "appearances can deceive"
l=IO.read $0; puts (1..9).map{|@i| "koan_#@i"+l[998,28] }, l[1343,37]; exit
}
].instance_eval{ self[rand(size)][] }
here is my solution,
i wrote it before people asked specific questions about the semantics
of some operations, which means the semantics doesn't exactly map to
the ones later specified by ara. [somehow shows some of the
disadvantages with test-driven development =)]. none of the things
mentioned should be particularly hard to change though.
anyway, it's not terribly long, not terribly short, and i haven't
tested the performance, but i've tried to layer my solution on top of
attr_accessor, instead of defining completely new methods, and by my
quick look through the other responses, not so many other decided to
it that way.
!g
q67-know.rb (2.24 KB)
Ok here is my solution:
class Module
def attribute(sym, *more, &blk)
attribute(*more, &blk) unless more.empty?
if sym.is_a?(Hash)
sym.each_pair { |sym, dval| attribute(sym, &(blk || lambda { dval })) }
else
iname = "@#{sym}"
define_method(sym) do
if instance_variables.include?(iname)
instance_variable_get(iname)
else
if blk then instance_eval(&blk) end
end
end
attr_writer(sym)
alias_method("#{sym}?", sym)
end
end
end
A polite and fairly readable, if completely uncommented 19 lines with
support for multiple symbols both in and out of the hash. And here is
the same solution painfully golfed into 4 lines of code -- by the way,
having unit tests are invaluable when golfing, I should have realized
this as golfing is just a deranged form of refactoring, but not being
a golfer this was enlightening 
class Module;def attribute(s,*r,&b);attribute(*r,&b) if r.any? ;(Hash===s)?
(s.each {|s,d|attribute(s,&(b||lambda{d}))}):(define_method(s){
instance_variables.include?("@"+s)?instance_variable_get("@"+s):(b&&
instance_eval(&b)||nil)};attr_writer(s);alias_method(s+"?",s));end;end
pth
Awesome quiz! Here is my solution, quite ugly I'm afraid.
Meta_value = {}
def attribute(name, &block)
(name.is_a?(Hash) ? name : {name => nil}).each do |key, value|
define_method(key.to_sym) do
if Meta_value[[self, key]].nil?
Meta_value[[self, key]] = (block_given? ? instance_eval(&block) : value)
else
Meta_value[[self, key]]
end
end
define_method((key + "=").to_sym) {|val| Meta_value[[self, key]] = val}
define_method((key + "?").to_sym) {not Meta_value[[self, key]].nil?}
end
end
···
--
Michael Ulm
R&D Team
ISIS Information Systems Austria
tel: +43 2236 27551-219, fax: +43 2236 21081
e-mail: michael.ulm@isis-papyrus.com
Visit our Website: www.isis-papyrus.com
---------------------------------------------------------------
This e-mail is only intended for the recipient and not legally
binding. Unauthorised use, publication, reproduction or
disclosure of the content of this e-mail is not permitted.
This email has been checked for known viruses, but ISIS accepts
no responsibility for malicious or inappropriate content.
---------------------------------------------------------------
I know. When Ara originally sent it to me, I couldn't stop fiddling with it. I'm sure he got tired of my emails pretty quick. 
James Edward Gray II
···
On Feb 17, 2006, at 11:15 AM, Ezra Zygmuntowicz wrote:
This quiz is way cool.
they wouldn't be koans otherwise! 
-a
···
On Sat, 18 Feb 2006, [ISO-8859-1] Christoffer Lernö wrote:
If I have a complaint about this quiz it's that I solved some of the parts
without even understanding them fully.
--
judge your success by what you had to give up in order to get it.
- h.h. the 14th dali lama
i generally make a block the winner because it's bigger visually and harder to
type - ergo one generally meant it if one typed it. whereas a hash is easy to
accidentally pass using
attribute(*args){ Time::now }
^
hash in here
suppose you could throw an error too - not my style - but it makes sense.
cheers.
-a
···
On Sat, 18 Feb 2006, Patrick Hurley wrote:
On 2/17/06, Ruby Quiz <james@grayproductions.net> wrote:
#
# 'attribute' must provide a method for providing a default value as hash
#
#
# 'attribute' must provide a method for providing a default value as block
# which is evaluated at instance level
#
<pedantic>
What if they provide both?
</pedantic>
--
judge your success by what you had to give up in order to get it.
- h.h. the 14th dali lama
This was not my first solution. It wasn't until Timothy Goddard posted his benchmarking method I realized I ought to be supporting multiple variables for a single call. This wasn't needed for passing the koans, but I thought it would be neat to do.
Thanks for a fun quiz!
/Christoffer
def attribute(*definition, &block)
raise "Name missing when creating attribute" unless definition.length > 0
definition.each do |entry|
if entry.respond_to?(:to_hash)
entry.to_hash.each_pair { |key, value| insert_attribute(key, value, block) }
else
insert_attribute(entry, nil, block)
end
end
end
def insert_attribute(name, value, block)
default_value = block ? "instance_eval(&block)" : "value"
begin
attr_writer name.to_s
eval("define_method(:#{name}) { return @#{name} if defined? @#{name}; @#{name} = #{default_value}}")
eval("define_method(:#{name}?) { self.#{name} != nil }") # this could also simply alias the getter and still pass.
rescue SyntaxError
raise "Illegal attribute name '#{name}'"
end
end
The sane:
class Module
def attribute(arg, val=nil, &blk)
if arg.is_a?(Hash)
arg.each{|k,v| attribute(k,v)}
return
end
define_method(arg) do ||
if instance_variables.include?("@#{arg}")
instance_variable_get("@#{arg}")
else
blk ? instance_eval(&blk) : val
end
end
define_method("#{arg}?"){|| !send(arg).nil?}
attr_writer(arg)
end
end
The insane:
class Module
def attribute(a, &b)
b or return (Hash===a ? a : {a=>nil}).each{|k,v| attribute(k){v}}
define_method(a){(x=eval("@#{a}")) ? x[0] : instance_eval(&b)}
define_method("#{a}?"){!send(a).nil?}
define_method("#{a}="){|v| instance_variable_set("@#{a}", [v])}
end
end
Florian Groß <florgro@gmail.com> writes:
Ruby Quiz wrote:
# 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.
Here we go then.
I have a golfed (8 lines) and a regular solution (21 lines) and
generally do the simplest thing that makes the tests work.
Got it to run in two tries by the way. I failed the fourtytwo test at
first. After that everything worked fine. 
Here's my solution, done in two tries too. Took about 15 minutes, I
think.
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
And thanks for a great quiz. Being able to do things test first rules!
Yeah, it really was a fun quiz.
···
--
Christian Neukirchen <chneukirchen@gmail.com> http://chneukirchen.org