[QUIZ] metakoans.rb (#67)

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.

This quiz is way cool.

-Ezra

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

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 :slight_smile:

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 :slight_smile:

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

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

-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

Here's mine

knowledge.rb (598 Bytes)

···

--
Sylvain Joyeux

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

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