Metakoans.rb (#67)

The three rules of Ruby Q.:

  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 Q. by submitting ideas as often as you can:

http://www.rubyquiz.com/

  1. Enjoy!

Suggestion: A [QUIZ] in the subject of emails about the problem helps
everyone
on Ruby T. 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.

This quiz is way cool.

-Ezra

On Feb 17, 2006, at 11:15 AM, Ezra Z. wrote:

This quiz is way cool.

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. :wink:

James Edward G. II

James G. wrote:

On Feb 17, 2006, at 11:15 AM, Ezra Z. wrote:

This quiz is way cool.

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. :wink:

And the test framework included with it is priceless. I like the fact
it executes the koans in order and stops at the first problem. This is
perfect for a “teaching” test framework, where you are using it to
introduce one concept at a time. I wish I had it for the continuations
talk at last year’s ruby conf. Ara, I see a need for a Test::Koan
library.

– Jim W.

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.

they wouldn’t be koans otherwise! :wink:

-a

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

Very interesting quiz :slight_smile:

One small question:

o.a? # query - true if @a

That should be strictly ==true (or ==false when not @a), right? not
something else which would evaluate to true in the assert…
I’m asking because all the asserts are like “assert{ c.a? }” ,and none
of them test “assert{ c.a? == true }”

On 2/17/06, Ruby Q. [email protected] 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
    #
What if they provide both?

pth

On Sat, 18 Feb 2006, Patrick H. wrote:

What if they provide both?

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, Sander L. wrote:

Very interesting quiz :slight_smile:

One small question:

o.a? # query - true if @a

That should be strictly ==true (or ==false when not @a), right? not
something else which would evaluate to true in the assert…
I’m asking because all the asserts are like “assert{ c.a? }” ,and none
of them test “assert{ c.a? == true }”

since only false and nil are failed predicates in ruby i find it quite
uesful
for boolean methods to return a value

if((sql = conn.commited?))
log sql
end

etc. i avoided ‘== true’ test to explicitly allow this behaviour. it’s
certainly fine to only return true, however. it’s a religious choice in
the
end.

regards.

-a

On 2/17/06, [email protected] [email protected] wrote:

    #

attribute(*args){ Time::now }

judge your success by what you had to give up in order to get it.

  • h.h. the 14th dali lama

Thanks ok last question (probalby), if remove_instance_variable is
called, and the attribute is subsequently accessed is it reset to the
default value?

Thanks again for a good quiz
pth

On Sat, 18 Feb 2006, Patrick H. wrote:

Thanks ok last question (probalby), if remove_instance_variable is called,
and the attribute is subsequently accessed is it reset to the default value?

good question. my version does not - the default is set once only. i
would
think re-setting an @var when remove_instance_variable has been called
would
be suprising behaviour - but it’s up to you.

Thanks again for a good quiz

sure. it was pretty fun to write too!

cheers.

-a

On 2/17/06, [email protected] [email protected] wrote:

sure. it was pretty fun to write too!

cheers.

Awesome quiz. For a while there, I didn’t think I was going to
succeed. My favorite part is how removing code often let me pass more
tests. Very zen.

Quoting [email protected]:

On Sat, 18 Feb 2006, Patrick H. wrote:

Thanks ok last question (probalby), if remove_instance_variable
is called, and the attribute is subsequently accessed is it
reset to the default value?

good question. my version does not - the default is set once
only. i would think re-setting an @var when
remove_instance_variable has been called would be suprising
behaviour - but it’s up to you.

Depends. If you think of it in terms of the default value being
shadowed by @var if it exists, returning to the default value after
a remove_instance_variable would seem reasonable.

This indirectly brings to mind another question, though – when and
how often is the block supposed to be called, if given? Once?
Per-class? Per-object? Per-get?

-mental

On 2/17/06, Wilson B. [email protected] wrote:

Awesome quiz. For a while there, I didn’t think I was going to
succeed. My favorite part is how removing code often let me pass more
tests. Very zen.

Amen, I ran into that too :slight_smile:

Jacob F.

(Final result, 42 lines, with whitespace)

On 2/17/06, Jacob F. [email protected] wrote:

Just got mine down to 30. I’m really looking forward to seeing the
shortest solution, because I have a feeling it’s going to humiliate
that.

On 2/17/06, Wilson B. [email protected] wrote:

On 2/17/06, Jacob F. [email protected] wrote:

(Final result, 42 lines, with whitespace)

Just got mine down to 30.

Ah, but you see, the number 42 is intricately related to the Zen of
this quiz. Just look in the koans… :slight_smile: I could have shrunk it a bit
more by compressing some blocks into one-liners, removing whitespace,
etc. But when, after making the final cruft-eliminating refactor, it
just happened to be 42 lines, I knew it was a sign. :wink:

Jacob F.

On Sat, 18 Feb 2006 [email protected] wrote:

remove_instance_variable has been called would be suprising
behaviour - but it’s up to you.

Depends. If you think of it in terms of the default value being
shadowed by @var if it exists, returning to the default value after
a remove_instance_variable would seem reasonable.

This indirectly brings to mind another question, though – when and
how often is the block supposed to be called, if given? Once?
Per-class? Per-object? Per-get?

per-object - where object may be a class (singleton method).

-a