Metakoans.rb (#67)

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

“Sander L.” [email protected] writes:

On 2/19/06, George O. [email protected] 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?'.

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: [email protected]
Visit our Website: www.isis-papyrus.com

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: [email protected]
Visit our Website: www.isis-papyrus.com

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

James Edward G. II

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

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

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: [email protected]
Visit our Website: www.isis-papyrus.com

Michael U. [email protected] 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.

On 2/20/06, George O. [email protected] 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.

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

Quoting ToRA [email protected]:

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

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

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.

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

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

“Patrick H.” [email protected] writes:

On 2/20/06, George O. [email protected] 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… :slight_smile:

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)

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: [email protected]
Visit our Website: www.isis-papyrus.com

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

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

On 2/21/06, Ross B. [email protected] 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.

On 2/19/06, Adam S. [email protected] 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