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.
James G. (Guest)
on 2006-02-17 15:58
(Received via mailing list)
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/

3.  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
Matthew M. (Guest)
on 2006-02-17 16:46
(Received via mailing list)
Wow...  I feel somewhat enlighted just reading the quiz...
Mike H. (Guest)
on 2006-02-17 17:35
(Received via mailing list)
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.
Ezra Z. (Guest)
on 2006-02-17 19:15
(Received via mailing list)
This quiz is way cool.

-Ezra
James G. (Guest)
on 2006-02-17 20:19
(Received via mailing list)
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.  ;)

James Edward G. II
Jim W. (Guest)
on 2006-02-17 20:48
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.  ;)

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.
Christoffer Lernö (Guest)
on 2006-02-17 20:49
(Received via mailing list)
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
unknown (Guest)
on 2006-02-17 21:17
(Received via mailing list)
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!  ;-)

-a
Sander L. (Guest)
on 2006-02-18 00:07
(Received via mailing list)
Very interesting quiz :)

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 }"
Patrick H. (Guest)
on 2006-02-18 00:25
(Received via mailing list)
On 2/17/06, Ruby Q. <removed_email_address@domain.invalid> 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>

pth
unknown (Guest)
on 2006-02-18 00:31
(Received via mailing list)
On Sat, 18 Feb 2006, Patrick H. wrote:

> <pedantic>
>  What if they provide both?
> </pedantic>


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
unknown (Guest)
on 2006-02-18 00:37
(Received via mailing list)
On Sat, 18 Feb 2006, Sander L. wrote:

> Very interesting quiz :)
>
> 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
Patrick H. (Guest)
on 2006-02-18 00:40
(Received via mailing list)
On 2/17/06, removed_email_address@domain.invalid 
<removed_email_address@domain.invalid> 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
unknown (Guest)
on 2006-02-18 00:52
(Received via mailing list)
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
Wilson B. (Guest)
on 2006-02-18 02:02
(Received via mailing list)
On 2/17/06, removed_email_address@domain.invalid 
<removed_email_address@domain.invalid> 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.
unknown (Guest)
on 2006-02-18 02:08
(Received via mailing list)
Quoting removed_email_address@domain.invalid:

> 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
Jacob F. (Guest)
on 2006-02-18 02:29
(Received via mailing list)
On 2/17/06, Wilson B. <removed_email_address@domain.invalid> 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 :)

Jacob F.

(Final result, 42 lines, with whitespace)
Wilson B. (Guest)
on 2006-02-18 02:32
(Received via mailing list)
On 2/17/06, Jacob F. <removed_email_address@domain.invalid> 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.
Jacob F. (Guest)
on 2006-02-18 02:44
(Received via mailing list)
On 2/17/06, Wilson B. <removed_email_address@domain.invalid> wrote:
> On 2/17/06, Jacob F. <removed_email_address@domain.invalid> 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... :) 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. ;)

Jacob F.
unknown (Guest)
on 2006-02-18 03:23
(Received via mailing list)
Quoting Wilson B. <removed_email_address@domain.invalid>:

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

23, with copious whitespace.

It's like Taoist golf.  Strive without striving.

-mental
unknown (Guest)
on 2006-02-18 03:38
(Received via mailing list)
On Sat, 18 Feb 2006 removed_email_address@domain.invalid 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
unknown (Guest)
on 2006-02-18 03:38
(Received via mailing list)
On Sat, 18 Feb 2006, Wilson B. wrote:

>> (Final result, 42 lines, with whitespace)
>>
>
> 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.

42 is the clear winner - regardless of who goes shorter ;-)

-a
unknown (Guest)
on 2006-02-18 03:41
(Received via mailing list)
Quoting removed_email_address@domain.invalid:

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

Hmm, would semantics like Hash's default block be acceptable?

-mental
Wilson B. (Guest)
on 2006-02-18 03:50
(Received via mailing list)
On 2/17/06, removed_email_address@domain.invalid 
<removed_email_address@domain.invalid> wrote:
> >> Jacob F.
> >>
> >> (Final result, 42 lines, with whitespace)
> >>
> >
> > 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.
>
> 42 is the clear winner - regardless of who goes shorter ;-)
>

I got it down to 15, but I can pad it to 42 with some haiku. Heh.
Timothy G. (Guest)
on 2006-02-18 10:35
(Received via mailing list)
Mine's also 42 non-blank, non-comment lines. I can see many ways to
reduce that, but all would uglify the code or slow it down. By moving
some logic inside a define I could cut that to 35, but I prefer to do
as much of the logic out of define_methods as possible.
Christian N. (Guest)
on 2006-02-18 12:29
(Received via mailing list)
removed_email_address@domain.invalid writes:

> Quoting Wilson B. <removed_email_address@domain.invalid>:
>
>> 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.
>
> 23, with copious whitespace.
>
> It's like Taoist golf.  Strive without striving.

23 here too, I could go <20 easily, but this way it's nice to read.
Sylvain J. (Guest)
on 2006-02-18 13:26
(Received via mailing list)
I'm at 13
Patrick H. (Guest)
on 2006-02-18 16:42
(Received via mailing list)
I also have 13; however, two lines exceed 80 columns. Additionally, I
have no support for multiple attributes on one line -- seems logical
to have...
Sander L. (Guest)
on 2006-02-18 18:43
(Received via mailing list)
14 lines here, with no really long lines.
could reduce it to 8, but it's much easier to read this way.
unknown (Guest)
on 2006-02-18 19:50
(Received via mailing list)
Geez. Seeing all these tiny line counts tells me that I'm missing
something fundamental, and that I'm not likely to "get it" if I've
never done this type of programming before. How depressing. Why bother?
MenTaLguY (Guest)
on 2006-02-18 21:06
(Received via mailing list)
On Sat, 2006-02-18 at 23:41 +0900, Patrick H. wrote:
> I also have 13; however, two lines exceed 80 columns. Additionally, I
> have no support for multiple attributes on one line -- seems logical
> to have...

13 seems to be the lower limit without seriously golfing.  My "best"
solution at this point runs about 18 lines, but it'd be 13 if I took out
all the blank lines and one line that makes it faster but doesn't affect
the semantics.

-mental
Peter Thoman (Guest)
on 2006-02-18 22:58
(Received via mailing list)
My current attribute method is just 8 lines, but the whole solution is
16 (normal length lines).
But I'm not completely happy with my implementation so I'm looking
forward to seeing what other people did.

It has been said already, but it bears repeating: Great quiz - both the
idea and the execution.
It motivated me so much that this is the first quiz I actually
completed!

- Peter
unknown (Guest)
on 2006-02-18 23:34
(Received via mailing list)
In article <removed_email_address@domain.invalid>,
Christian N.  <removed_email_address@domain.invalid> wrote:
>> It's like Taoist golf.  Strive without striving.
>
>23 here too, I could go <20 easily, but this way it's nice to read.
>

I've got a very readable 18 lines.

I really enjoyed this quiz.

Phil
Kero (Guest)
on 2006-02-19 00:51
(Received via mailing list)
>> I also have 13; however, two lines exceed 80 columns. Additionally, I
>> have no support for multiple attributes on one line -- seems logical
>> to have...
>
> 13 seems to be the lower limit without seriously golfing.  My "best"
> solution at this point runs about 18 lines, but it'd be 13 if I took out
> all the blank lines and one line that makes it faster but doesn't affect
> the semantics.

Dunno about golfing, been snowboarding the whole week.

I'm near koan 8 with 28 lines, and I need sleep (failed at 7, while
wondering why the program knew I did [need sleep]; Then I solved 7 while
puzzling about 8, or actually ironing out an inconsistency in my
program;
maybe I am already asleep)

Anyway, what I wanted to post about: in the description/code there are a
few
asserts like this:
   assert{ (o.a = nil) == nil }
which will always be asserted, since Ruby evaluates 'a = b' to b, no
matter
what a is; specifically, it does not evaluate to the result of #a= in
code
like 'o.a = b'; imho, this makes perfect sense for statements like 'a =
b = c'

Code to show:

class X
  attr_reader :ha
  def ha=(val)
    @ha = "not what you expect"
  end
end
x = X.new
p x.ha = nil           # => nil
p x.ha                 # => "not what you expect"
p (x.ha = nil) == nil  # => true

Bye,
Kero.

PS: fun quiz!
Timothy G. (Guest)
on 2006-02-19 01:00
(Received via mailing list)
To take the focus off number of lines, let's take a look at bechmarks.
This was my benchmark script:

Benchmark::bm(12) do |x|
  x.report('attr') {100000.times {c = Class.new {attr :foo, :bar}}}
  x.report('attribute') {100000.times {c = Class.new {attribute :foo,
:bar}}}
end

And my results were:

                  user     system      total        real
attr          3.720000   0.000000   3.720000 (  3.724316)
attribute    25.750000   0.000000  25.750000 ( 25.790665)

It isn't that efficient, but I'd like to see how it compares.
Wilson B. (Guest)
on 2006-02-19 03:30
(Received via mailing list)
On 2/18/06, Timothy G. <removed_email_address@domain.invalid> wrote:
>
>                   user     system      total        real
> attr          3.720000   0.000000   3.720000 (  3.724316)
> attribute    25.750000   0.000000  25.750000 ( 25.790665)
>
> It isn't that efficient, but I'd like to see how it compares.
>

That's interesting. I didn't expect the difference to be quite this
big.  attr is impressively fast.
                  user     system      total        real
attr          7.250000   0.000000   7.250000 (  7.250000)
attribute    30.110000   0.000000  30.110000 ( 30.172000)

I'm looking forward to seeing some of the other solutions, for sure.
I had my hands halfway around a faster way, but couldn't quite make it
work.
Eero S. (Guest)
on 2006-02-19 05:47
(Received via mailing list)
On 2006.02.19 10:30, Wilson B. wrote:
> > And my results were:
>                   user     system      total        real
> attr          7.250000   0.000000   7.250000 (  7.250000)
> attribute    30.110000   0.000000  30.110000 ( 30.172000)
>
> I'm looking forward to seeing some of the other solutions, for sure.
> I had my hands halfway around a faster way, but couldn't quite make it
> work.

Yours actually looks very good. My first attempt is quite a bit off:

                    user     system      total        real
  attr          7.117188   0.000000   7.117188 (  7.381454)
  attribute    38.835938   0.031250  38.867188 ( 41.244430)

To factor out some machine differential, mine works out to be about
5.46 times slower, yours is 4.15 and Timothy is at 7.45. Another
interesting tidbit is that attr_accessor is about 1.5 to 2 times
slower than attr.

From another standpoint, it might be interesting to look at
access and update speeds. Here is the script:

  require 'benchmark'
  require 'knowledge'

  Benchmark.bm {|b|
    b.report('attr_accessor') {c = Class.new {attr_accessor :foo}.new;
c.foo = 2; 100_000.times {c.foo}}
    b.report('attribute-plain') {c = Class.new {attribute :foo}.new;
c.foo = 2; 100_000.times {c.foo}}
    b.report('attribute-default') {c = Class.new {attribute(:foo)
{2}}.new; 100_000.times {c.foo}}
  }

                         user     system      total        real
  attr_accessor      0.046875   0.000000   0.046875 (  0.052311)
  attribute-plain    0.343750   0.000000   0.343750 (  0.364738)
  attribute-default  0.460938   0.000000   0.460938 (  0.474879)

So there is clear order-of-magnitude difference there.


E
Harold H. (Guest)
on 2006-02-19 06:02
(Received via mailing list)
attr_* are written in c if I'm not mistaken. That might explain (at
least some of) the difference.

+1 on this being an excellent quiz. Thanks again Ara

-Harold
Ross B. (Guest)
on 2006-02-19 06:14
(Received via mailing list)
I wasn't going to get into benchmarking this one, but ...

On Sun, 2006-02-19 at 12:45 +0900, Eero S. wrote:
> > >
> > big.  attr is impressively fast.
>                     user     system      total        real
>   attr          7.117188   0.000000   7.117188 (  7.381454)
>   attribute    38.835938   0.031250  38.867188 ( 41.244430)
>
> To factor out some machine differential, mine works out to be about
> 5.46 times slower, yours is 4.15 and Timothy is at 7.45. Another
> interesting tidbit is that attr_accessor is about 1.5 to 2 times
> slower than attr.
>

I think maybe there is some confusion over the operation of 'attr' here,
the test above defines a single attribute, :foo. The second argument to
'attr' is evaluated as boolean, and determines whether a writer is
created also. So the test will make foo and foo= but not bar or bar= .
Consider the following IRB:

	class C
	  attr :one, :two
	end
	# => nil

	c = C.new
	# => #<C:0xb7e34534>

	c.one
	# => nil
	c.one = 'one'
	# => "one"
	c.two
	NoMethodError: undefined method `two' for #<C:0xb7e34534>
	        from (irb):11

>   }
>
>                          user     system      total        real
>   attr_accessor      0.046875   0.000000   0.046875 (  0.052311)
>   attribute-plain    0.343750   0.000000   0.343750 (  0.364738)
>   attribute-default  0.460938   0.000000   0.460938 (  0.474879)
>
> So there is clear order-of-magnitude difference there.

After modifying the original benchmark to use attr_accessor (for
completeness really, they're all implemented in C AFAIK so comparison is
pretty futile from the start ;)) and adding in the code above, I get:

### Creation ###
                          user     system      total        real
attr                  4.930000   0.040000   4.970000 (  5.066622)
attribute            16.080000   0.130000  16.210000 ( 16.429639)

### Use ###
                          user     system      total        real
attr_accessor         0.080000   0.010000   0.090000 (  0.123403)
attribute-plain       0.730000   0.000000   0.730000 (  0.777927)
attribute-default     0.700000   0.010000   0.710000 (  0.755691)
Greg M. (Guest)
on 2006-02-19 09:49
(Received via mailing list)
MenTaLguY wrote:

 > On Sat, 2006-02-18 at 23:41 +0900, Patrick H. wrote:
 >
 >> I also have 13; however, two lines exceed 80 columns. Additionally,
I
 >> have no support for multiple attributes on one line -- seems logical
 >> to have...
 >
 >
 >
 > 13 seems to be the lower limit without seriously golfing.  My "best"
 > solution at this point runs about 18 lines, but it'd be 13 if I took
out
 > all the blank lines and one line that makes it faster but doesn't
affect
 > the semantics.


Mine's at 9 without any real golfing. No newlines though. Maximum line
length's 67, and that's the largest by far. I use "cond ? true : false"
4 times though, and that could be cleaner, I imagine.

However: While it passes all the tests, it might have some poor flaws in
real world use.

i.e:
   * Anything with the same hash would have the same attributes. (i.e: 2
string objects containing the same text)
   * Every instance of the class that uses the attribute is kept in
memory so long as the class is.
   * foo? returns the value, instead of exact true/false. While this is
legit, it can't be used in (a.foo? == false) conditionals. But that's
unrubylike anyway :D

- Greg
Mike H. (Guest)
on 2006-02-19 12:32
(Received via mailing list)
MenTaLguY wrote:

>solution at this point runs about 18 lines, but it'd be 13 if I took out
>all the blank lines and one line that makes it faster but doesn't affect
>the semantics.
>
>-mental
>
>
>
>
>
Yea, I don't usually golf, so I'm torn as to what's reasonable and
what's golfing, in terms of line count.  Right now I'm at 17, and I got
it down to 13 or 14 before changing it back cause I found the new code
aesthetically displeasing.
Christian N. (Guest)
on 2006-02-19 12:35
(Received via mailing list)
"Harold H." <removed_email_address@domain.invalid> writes:

> attr_* are written in c if I'm not mistaken. That might explain (at
> least some of) the difference.

Not only that, it directly circumvents further method calls and
accesses the instance variable table directly.  No way to beat that in
pure Ruby.

> +1 on this being an excellent quiz. Thanks again Ara

Full ack.
Luke B. (Guest)
on 2006-02-19 16:07
(Received via mailing list)
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 B.
Christoffer Lernö (Guest)
on 2006-02-19 16:20
(Received via mailing list)
This was not my first solution. It wasn't until Timothy G.
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
Florian GroÃ? (Guest)
on 2006-02-19 16:23
(Received via mailing list)
Ruby Q. 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!
Ross B. (Guest)
on 2006-02-19 16:32
(Received via mailing list)
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
Sylvain J. (Guest)
on 2006-02-19 16:38
(Received via mailing list)
Here's mine
George O. (Guest)
on 2006-02-19 16:44
(Received via mailing list)
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
Sander L. (Guest)
on 2006-02-19 18:16
(Received via mailing list)
> On 2/19/06, George O. <removed_email_address@domain.invalid> wrote:
> ...
> define_method("#{arg}?"){|| !send(arg).nil?}
> ...
what about send(arg) == false ?


Here is my solution, pretty similar to the one george posted, though
it doesn't support multiple attributes.

class Module
  def attribute(a,&blk)
    a,val = a.to_a[0] if a.kind_of? Hash
    attr_accessor a
    define_method(a+'?') { !!send(a) }
    define_method(a) {
      if instance_variables.include?('@'+a)
        instance_variable_get('@'+a)
      else
        val || instance_eval(&blk)
      end
    } if val || blk
  end
end
Eero S. (Guest)
on 2006-02-19 18:20
This solution is by memory, it seems my website
is down. I will post a corrected version if there
are any bugs in this but hopefully the idea is
clear.

# knowledge.rb
class Module
  def attribute(*args, &block)
    # Normalise
    args = args.inject([]) {|memo, arg|
             if arg.kind_of? Hash
               arg.map {|k,v| memo << [k, (block or lambda {v})]}; memo
             else
               memo << [arg, (block or lambda {instance_variable_get
"@#{arg}"})]
             end
           }

    # Generate
    args.each {|name, block|
      # Retrieval
      define_method("#{name}")  {instance_variable_get "@#{name}" or
instance_eval &block}
      # Query
      define_method("#{name}?") {send "#{name}"}

      # Assignment
      define_method("#{name}=") {|value|
        # Generate a simple accessor to avoid problems with nils and
defaults
        class << self; self; end.send 'define_method', "#{name}", lambda
{value} unless value
        instance_variable_set "#{name}", value
      }
    }
  end                    # attribute
end                      # class Module


Should handle multiple arguments, block supercedes hash
parameters and so on. Not very pretty or particularly fast.

E
Jim F. (Guest)
on 2006-02-19 18:28
(Received via mailing list)
First the benchmarks:

% ruby bm1.rb
                   user     system      total        real
attr         15.250000   0.130000  15.380000 ( 18.405451)
attribute    64.520000   0.730000  65.250000 ( 74.519375)
irb(main):001:0> 74.519/18.405

=>   a 4X slowdown

  % ruby bm2.rb
       user     system      total        real
attr_accessor  0.130000   0.000000   0.130000 (  0.185192)
attribute-plain  0.370000   0.010000   0.380000 (  0.662546)
attribute-default  0.590000   0.010000   0.600000 (  0.794125)

Then the comments:

Nice quiz. I have been using ruby for 6 years and have never needed
define_method.
The binding of the instance method fortytwo from the class scope was
something
I hadn't thought of. I now understand (I think) why one would use
define_method.

I also consider it a success when I can write code that doesn't
include all a bunch of
call to instance_variable_set/get. All in all, I think the code came
out pretty clean.

And lastly the code:

class Module
   def attribute(*parms, &block)
     return parms[0].each { |p,v| attribute(p) {v} } if parms
[0].kind_of?(Hash)

     parms.each { |parm|
       define_method("__#{parm}__", block) unless block.nil?

       class_eval %{
         attr_writer :#{parm}

         def #{parm}
           defined?(@#{parm}) ? @#{parm} : __#{parm}__
         end

         def #{parm}?
           (defined?(@#{parm}) || defined?(__#{parm}__)) && !#
{parm}.nil?
         end
       }
     }
   end
end

== Or 10 lines of golf

class Module
   def attribute(*parms, &block)
     return parms[0].each { |p,v| attribute(p) {v} } if parms
[0].kind_of?(Hash)
     parms.each { |parm|
       define_method("__#{parm}__", block) unless block.nil?
       class_eval %{attr_writer :#{parm}
         def #{parm};defined?(@#{parm}) ? @#{parm} : __#{parm}__;end
         def #{parm}?;(defined?(@#{parm}) || defined?(__#{parm}__))
&& !#{parm}.nil?;end}}
   end
end


Jim F.
Christian N. (Guest)
on 2006-02-19 18:37
(Received via mailing list)
Florian Groß <removed_email_address@domain.invalid> writes:

> 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 N. (Guest)
on 2006-02-19 19:08
(Received via mailing list)
Jim F. <removed_email_address@domain.invalid> writes:

> Nice quiz. I have been using ruby for 6 years and have never needed
> define_method.

%-)
Meador I. (Guest)
on 2006-02-19 19:11
(Received via mailing list)
This is definitely a great quiz. I had a ton of fun working and and
learned a great deal about Ruby as well. I am relatively a Nuby.
Anyway, here is what I came up with:

class Module
  def attribute(*objects, &block)
    objects.each { |object|
      attr = object.is_a?(Hash) ? object : {object => nil}
      symbol = attr.keys[0]
      default = block || lambda { attr[symbol] }
      define_method("#{symbol}?") { instance_eval &default }
      class_eval "alias #{symbol} #{symbol}?"
      define_method("#{symbol}=") { |value|
        instance_eval %{
           def #{symbol}?; @#{symbol}; end
           alias #{symbol} #{symbol}?
           @#{symbol} = value
           }
         }
       }
     end
end
James G. (Guest)
on 2006-02-19 19:17
(Received via mailing list)
On Feb 19, 2006, at 8:21 AM, Florian Groß wrote:

> Ruby Q. 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.

Here's what I coded up, back when Ara sent me the quiz.  Everyone
else found much prettier code though.  ;)

James Edward G. II

#!/usr/local/bin/ruby -w

class Module
   def attribute( name, &block )
     if name.is_a? Hash
       name.each do |attr_name, default|
         define_method(attr_name) do
           if instance_variables.include?("@#{attr_name}")
             instance_variable_get("@#{attr_name}")
           else
             default
           end
         end

         define_method("#{attr_name}=") do |value|
           instance_variable_set("@#{attr_name}", value)
         end

         define_method("#{attr_name}?") do
           send(attr_name) ? true : false
         end
       end
     elsif block
       define_method(name) do
         if instance_variables.include?("@#{name}")
           instance_variable_get("@#{name}")
         else
           instance_eval(&block)
         end
       end

       define_method("#{name}=") do |value|
         instance_variable_set("@#{name}", value)
       end

       define_method("#{name}?") do
         send(name) ? true : false
       end
     else
       define_method(name) do
         instance_variable_get("@#{name}")
       end

       define_method("#{name}=") do |value|
         instance_variable_set("@#{name}", value)
       end

       define_method("#{name}?") do
         send(name) ? true : false
       end
     end
   end
end
Ryan L. (Guest)
on 2006-02-19 21:28
(Received via mailing list)
Here is mine. Nothing too extraordinary or different than other
solutions, but it is fairly short and readable.

class Module
  def attribute(x, &block)
    name, value = x.to_a[0] # produces a warning is x is a symbol
    ivar = "@#{name}"
    define_method(name) do
      if instance_variables.include?(ivar)
        instance_variable_get(ivar)
      else
        value || (instance_eval &block if block)
      end
    end
    attr_writer name
    define_method("#{name}?") { !!send(name) }
  end
end

As others have said, this was a cool quiz. While the meta-programming
part was interesting, I think this quiz is a better advertisement for
test-first coding than anything! Very cool and fun test suite. Thanks
Ara.

Ryan
Ilmari H. (Guest)
on 2006-02-19 21:37
(Received via mailing list)
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)][] }
Adam S. (Guest)
on 2006-02-19 22:19
(Received via mailing list)
My solution follows - I think it's pretty straightforward. 16 lines
without golf.  The only part I don't really like is the a_init_
sentinel I had to add to pass Koan9.  I messed around with redefining
the initializer in the call to a=, but I couldn't get that to work
right.
One thing I noted:  I could get through koans 1-5 with a `def a; 42;
end` :)

class Object
  def attribute name, &block
    init_proc = proc {nil}
    if block_given?
      init_proc = block
    elsif name.kind_of? Hash
      v = name.values.first
      name = name.keys.first
      init_proc = proc{v}
    end
    self.send(:define_method, "#{name}_init_", init_proc)
    self.class_eval "def #{name}; if !@#{name}&&!@#{name}_init_ then
@#{name}=#{name}_init_; @#{name}_init_=true; end; @#{name}; end"
    self.class_eval "def #{name}?; !(self.#{name}.nil?); end"
    self.class_eval "def #{name}=(v); @#{name}=v; end"
  end
end

-Adam
(Guest)
on 2006-02-19 23:50
(Received via mailing list)
Here's my solution.  It ends up at 13 lines, as long as you don't mind
one line going past 80 chars.  Kudos to Ara for such an interesting
quiz.

class Module
  def attribute sym, &blk
    name, dval = sym, false
    name, dval = sym.keys.first, sym.values.first if Hash === sym
    if blk
      dval = "def_value_#{sym}".to_sym
      define_method(dval, blk)
    end
    module_eval %( def #{name}; @#{name} ||= #{dval}; end )
    module_eval %( def #{name}=(val); if NilClass === val then @#{name}
= lambda{nil}.call; else @#{name} = val; end; end )
    module_eval %( def #{name}?; @#{name} ? true : false ; end)
  end
end

- Hitesh
http://www.jasani.org/
aurelianito (Guest)
on 2006-02-20 01:09
(Received via mailing list)
I have the longest solution shown. 50 LINES!

Here it is:
class Module
  def attribute( attrib, &block )
    if attrib.is_a? String
    then
      if (block_given?) then
        property_with_block_init(attrib, block )
      else
        property(attrib)
      end
    elsif attrib.is_a? Hash
      attrib.each_pair do
        |property, value|
        property_with_default( property, value )
      end
    else
    end
  end
  def property(name)
    self.module_eval %Q{
      attr_accessor name.to_sym
      def #{name}?
        not not #{name}
      end
    }
  end
  def property_with_block_init(name, block)
    property(name)
    self.module_eval do
      define_method( name.to_sym ) do
        if self.instance_variables.member?("@" + name) then
          self.instance_variable_get("@" + name)
        else
          instance_eval( &block )
        end
      end
    end
  end
  def property_with_default( name, value )
    property(name)
    self.module_eval do
      define_method( name.to_sym ) do
        if self.instance_variables.member?("@" + name) then
          self.instance_variable_get("@" + name)
        else
          value
        end
      end
    end
  end
end
James G. (Guest)
on 2006-02-20 02:46
(Received via mailing list)
On Feb 19, 2006, at 1:36 PM, Ilmari H. wrote:

> Here's mine, it's 8 one-liners of which one is randomly picked:

[snip - powerful meditation solving code]

This solution was priceless.  ;)

James Edward G. II
Gustav M. (Guest)
on 2006-02-20 15:43
(Received via mailing list)
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
George O. (Guest)
on 2006-02-20 16:35
(Received via mailing list)
"Sander L." <removed_email_address@domain.invalid> writes:

>> On 2/19/06, George O. <removed_email_address@domain.invalid> wrote:
>> ...
>> define_method("#{arg}?"){|| !send(arg).nil?}
>> ...
> what about send(arg) == false ?

Good point!  The koans didn't question that, though.

Indeed, `!!send(arg)' is a better solution.  So is using `defined?'
instead of `instance_variables.include?'.
Michael U. (Guest)
on 2006-02-20 16:41
(Received via mailing list)
Christian N. wrote:

>>
> think.
>     a = a.to_sym
>     define_method("#{a}?") { !!__send__(a) }
>   end
> end
>

This demonstrates a problem I have with many solutions -- taking
Florians as an example.

class Test
   attribute 'a'
end

tst.a?         # => false   OK
tst.a = false  # => false   OK
tst.a?         # => false   This should be true


Regards,

Michael

--
Michael U.
R&D Team
ISIS Information Systems Austria
tel: +43 2236 27551-219, fax: +43 2236 21081
e-mail: removed_email_address@domain.invalid
Visit our Website: www.isis-papyrus.com
Michael U. (Guest)
on 2006-02-20 16:41
(Received via mailing list)
Michael U. wrote:

--snip--
>
> This demonstrates a problem I have with many solutions -- taking
> Florians as an example.
>
--snip--

s/Florian/Christian/

So sorry,

Michael


--
Michael U.
R&D Team
ISIS Information Systems Austria
tel: +43 2236 27551-219, fax: +43 2236 21081
e-mail: removed_email_address@domain.invalid
Visit our Website: www.isis-papyrus.com
James G. (Guest)
on 2006-02-20 16:47
(Received via mailing list)
On Feb 20, 2006, at 8:38 AM, Michael U. wrote:

> This demonstrates a problem I have with many solutions -- taking
> Florians as an example.
>
> class Test
>   attribute 'a'
> end
>
> tst.a?         # => false   OK
> tst.a = false  # => false   OK
> tst.a?         # => false   This should be true

I believe you are making the mistake that you expect attr_meth?() to
tell you if an attribute is set.  That's not the intention.  attr_meth
() returns true if the attribute holds a true value.

I know this because I made the exact same mistake and Ara corrected
me.  ;)

James Edward G. II
Michael U. (Guest)
on 2006-02-20 16:56
(Received via mailing list)
James Edward G. II wrote:

>> tst.a = false  # => false   OK
>> tst.a?         # => false   This should be true
>
>
> I believe you are making the mistake that you expect attr_meth?() to
> tell you if an attribute is set.  That's not the intention.  attr_meth
> () returns true if the attribute holds a true value.
>
> I know this because I made the exact same mistake and Ara corrected
> me.  ;)

I just checked the metakoans.rb file, and see that you are right:

#   o.a?      # query  - true if @a

Had I defined the interface, this would behave differently. I guess
this shows that I am not enlightened yet.

Regards,

Michael


--
Michael U.
R&D Team
ISIS Information Systems Austria
tel: +43 2236 27551-219, fax: +43 2236 21081
e-mail: removed_email_address@domain.invalid
Visit our Website: www.isis-papyrus.com
Patrick H. (Guest)
on 2006-02-20 17:39
(Received via mailing list)
On 2/20/06, George O. <removed_email_address@domain.invalid> wrote:
> ... is a better solution.  So is using `defined?'
> instead of `instance_variables.include?'.

The problem with defined? is that it will only work in an eval
context, that is if you use a block with define_method (which I
personally prefer), then defined?(str_rep_of_var) is always an
expression, so you need to use the instance_variables.include?

Of course if someone has a trick (beyond eval("defined? #{ivar}") I am
interested.
Christian N. (Guest)
on 2006-02-20 18:25
(Received via mailing list)
Michael U. <removed_email_address@domain.invalid> writes:

> So sorry,
Umm, the quiz says:

	#   o.a?      # query  - true if @a

If @a is nil (or unset) or false, a? is false; else, a? is true.
YMMV, but that's what I think makes most sense.
Patrick H. (Guest)
on 2006-02-20 19:11
(Received via mailing list)
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
ToRA (Guest)
on 2006-02-20 21:05
(Received via mailing list)
Hey,

Genius quiz. I'm trying with my solution to avoid using def/class etc.
and solve just using the meta-programming methods (and without evaling
random strings); "just for fun", though I'm coming unstuck with
defining a method that can accept a block as a second argument....

Module.instance_eval do
   define_method :attribute do |arg|
....

block_given? and yield always fail, and "do |arg,&block|" isn't legal
syntax...any suggestions?

Cheers,

Tris
unknown (Guest)
on 2006-02-20 21:11
(Received via mailing list)
Quoting ToRA <removed_email_address@domain.invalid>:

> block_given? and yield always fail, and "do |arg,&block|" isn't
> legal syntax...any suggestions?

Blocks that take blocks aren't possible in Ruby 1.8; you'll have to
use def and/or eval.

-mental
ToRA (Guest)
on 2006-02-20 21:49
(Received via mailing list)
> Blocks that take blocks aren't possible in Ruby 1.8; you'll have to
> use def and/or eval.

Ah, yeah...just found another post explaining this. :(

Oh well, my solution, don't think there's anything too novel in it
though...

Module.module_eval do
    def attribute(arg,&block)   # I bow to the impossible


    case when block_given?
      default_value = true
    when Hash === arg
      inst_var_name, default_value = arg.to_a.flatten
    end

    inst_var_name ||= arg
    inst_var = "@#{inst_var_name || arg}"

    define_method inst_var_name do
      if default_value and not instance_variables.member?(inst_var)
        if block_given?
          instance_variable_set inst_var, instance_eval(&block)
        else
          instance_variable_set inst_var, default_value
        end
      end


      instance_variable_get inst_var

    end



    define_method("#{inst_var_name}=") do |v|

      instance_variable_set inst_var, v

    end



    alias_method("#{inst_var_name}?", inst_var_name)

  end

end
Timothy G. (Guest)
on 2006-02-20 22:54
(Received via mailing list)
This is my solution after a bit more trimming this morning:

class Module
  def attribute(*arguments, &block)
    # This will store attribute names for adding writers and testers
    attributes = []

    # Iterate over arguments, defining a getter function and adding
each attribute to the attributes list.
    arguments.each do |argument|
      if argument.is_a? Hash # Defaults from hash values
        # Our argument is a hash. Iterate over the hash, treating keys
as attributes and values as defaults.
        argument.each do |attribute, default|
          attributes << attribute.to_s

          # Define getter with fixed default
          define_method((attribute).to_sym) do
            return default unless instance_variables.include?('@' +
attribute.to_s)
            self.instance_variable_get('@' + attribute.to_s)
          end
        end # argument.each
      elsif block # Default from block
        attributes << argument.to_s

        # Our default is a block which should be instance_evaled to get
a default value.
        define_method((argument).to_sym) do
          return instance_eval(&block) unless
instance_variables.include?('@' + argument.to_s)
          self.instance_variable_get('@' + argument.to_s)
        end
      else # No default
        attributes << argument.to_s
        define_method((argument).to_sym) do
          self.instance_variable_get('@' + argument.to_s)
        end
      end # if argument.is_a? Hash ... elsif block ... end
    end # arguments.each

    # Iterate over the attributes, defining our writer and tester
methods.
    attributes.each do |attribute|
      # Define the writer.
      attr_writer attribute.to_sym

      # Define the tester
      define_method((attribute + '?').to_sym) do
        self.send(attribute) ? true : false
      end
    end # attributes.each
  end # def attribute
end # class Module

I decided to allow the hash to overrule the block for a simple reason:
only one block can be provided. This allows me to use (for example)

attribute :foo, :bar => 3 do
  bar * 7
end

This means that we can give fixed defaults to some attributes while
using a block for those without them.
George O. (Guest)
on 2006-02-21 05:34
(Received via mailing list)
"Patrick H." <removed_email_address@domain.invalid> writes:

> On 2/20/06, George O. <removed_email_address@domain.invalid> wrote:
>> ... is a better solution.  So is using `defined?'
>> instead of `instance_variables.include?'.
>
> The problem with defined? is that it will only work in an eval
> context, that is if you use a block with define_method (which I
> personally prefer), then defined?(str_rep_of_var) is always an
> expression, so you need to use the instance_variables.include?

Well nothing's perfect... :-)

But from a performance perspective, defined? scales much better with
the number of instance variables hanging around.  Benchmarks reveal
that the balance point was at around 5 ivars for me.

require 'benchmark'

class C
  def initialize n
    n.times do |i|
      instance_variable_set("@x#{i}", i)
    end
    ivar = instance_variables[n/2]  # average-case for 'include?'
    Benchmark.bm do |b|
      b.report('defined?'){100000.times{ eval("defined?(@x0)")}
}
      b.report('include?'){100000.times{
instance_variables.include?(ivar)} }
    end
  end
end

(1..10).each do |i|
  puts " #{i} ".center(70, '=')
  C.new(i)
end

----------------------------------------------------------------------

================================= 1 ==================================
      user     system      total        real
defined?  0.340000   0.000000   0.340000 (  0.343841)
include?  0.130000   0.000000   0.130000 (  0.131621)
================================= 2 ==================================
      user     system      total        real
defined?  0.360000   0.000000   0.360000 (  0.355125)
include?  0.220000   0.000000   0.220000 (  0.229211)
================================= 3 ==================================
      user     system      total        real
defined?  0.390000   0.000000   0.390000 (  0.402486)
include?  0.240000   0.000000   0.240000 (  0.243938)
================================= 4 ==================================
      user     system      total        real
defined?  0.340000   0.000000   0.340000 (  0.347124)
include?  0.310000   0.000000   0.310000 (  0.304621)
================================= 5 ==================================
      user     system      total        real
defined?  0.350000   0.000000   0.350000 (  0.353857)
include?  0.340000   0.000000   0.340000 (  0.346883)
================================= 6 ==================================
      user     system      total        real
defined?  0.350000   0.000000   0.350000 (  0.344404)
include?  0.410000   0.000000   0.410000 (  0.409498)
================================= 7 ==================================
      user     system      total        real
defined?  0.370000   0.000000   0.370000 (  0.373150)
include?  0.490000   0.000000   0.490000 (  0.502982)
================================= 8 ==================================
      user     system      total        real
defined?  0.350000   0.000000   0.350000 (  0.352441)
include?  0.510000   0.000000   0.510000 (  0.515715)
================================= 9 ==================================
      user     system      total        real
defined?  0.350000   0.000000   0.350000 (  0.350805)
include?  0.560000   0.000000   0.560000 (  0.555990)
================================= 10 =================================
      user     system      total        real
defined?  0.350000   0.000000   0.350000 (  0.354339)
include?  0.650000   0.000000   0.650000 (  0.663272)
Jeremy H. (Guest)
on 2006-02-21 09:03
(Received via mailing list)
Great quiz Ara, and here's my solution.  For some reason I wasn't using
instance_eval correctly, so I ended up defining an initalizer method for
the attribute.  Also, I seem to have implemented the attr? method in a
manner different than most folks.

enjoy,

-jeremy
Michael U. (Guest)
on 2006-02-21 09:43
(Received via mailing list)
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 U.
R&D Team
ISIS Information Systems Austria
tel: +43 2236 27551-219, fax: +43 2236 21081
e-mail: removed_email_address@domain.invalid
Visit our Website: www.isis-papyrus.com
Adam S. (Guest)
on 2006-02-21 20:25
(Received via mailing list)
On 2/19/06, Adam S. <removed_email_address@domain.invalid> wrote:
>   The only part I don't really like is the a_init_
> sentinel I had to add to pass Koan9.

Ok, after looking at some of the other solutions, I've refined my
solution.
It now handles multiple attributes with initializers.
I think I disagree with Ara about what to do when provided with both a
block and a hash.  With multiple attributes, it makes more sense to me
if  the block acts as a default, rather than an override, so that you
get the following:
t = Class.new {attribute(:a,:b=>4,:c=>2){10*b+c} }.new
[t.a, t.b, t.c] => [42, 4, 2]
To get Ara's preferred behaviour, just add `,&block` before the `and`
on the 4th line.

I notice that most people extended Module, while I extended Object.
Is there a reason to prefer one over the other?

-Adam

#-------Knowledge.rb
#
class Object
  def attribute *names, &block
    names.each do |name|
      attribute *name.map{|k,v|[k,v]} and next if name.kind_of? Hash
      name,v = name
      class_eval "def #{name};"+
            "@#{name}=(defined?(@#{name}) ? @#{name} : #{name}_init_);
end"
      class_eval "def #{name}?; !(self.#{name}.nil?); end"
      class_eval "def #{name}=(v); @#{name}=v; end"
      private; define_method("#{name}_init_", block || proc {v})
    end
  end
end
Ross B. (Guest)
on 2006-02-21 20:52
(Received via mailing list)
On Wed, 2006-02-22 at 03:23 +0900, Adam S. wrote:

> I notice that most people extended Module, while I extended Object.
> Is there a reason to prefer one over the other?
>

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.

>       class_eval "def #{name}=(v); @#{name}=v; end"
>       private; define_method("#{name}_init_", block || proc {v})
>     end
>   end
> end
>
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.
Adam S. (Guest)
on 2006-02-21 23:23
(Received via mailing list)
On 2/21/06, Ross B. <removed_email_address@domain.invalid> wrote:
> 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.

aha.
I am not yet sufficiently enlightened about metaclasses.
I need more koans.
This topic is locked and can not be replied to.