[/QUIZ] metakoans.rb (#67)

L

Luke Blanshard

--------------090205020702090600080900
Content-Type: text/plain; charset=ISO-8859-1; format=flowed
Content-Transfer-Encoding: 7bit

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


--------------090205020702090600080900
Content-Type: text/plain;
name="knowledge.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="knowledge.rb"

style = :code # or :string

case style
when :string
class Module
def attribute desc, &block
if desc.is_a? Hash
name = desc.keys[0]
default = desc[name]
raise "Hash or block, not both" if block
else
name, default = desc, nil
end
module_eval <<-"end;", __FILE__, __LINE__
def #{name}?; #{name} != nil end # Permanent definition of query method
def __#{name}__ivarget; @#{name} end # ivar getter and setter
def __#{name}__ivarset v; @#{name}=v end
# Initial def of attr reader
define_method(name) { self.#{name} = (block ? instance_eval(&block) : default) }
def #{name}= value # Initial def of attr writer
(class << self; self; end).class_eval do
alias_method :#{name}, :__#{name}__ivarget # Subsequent calls use ivar getter and setter
alias_method :#{name}=, :__#{name}__ivarset
end
@#{name} = value
end
end;
end
end

when :code
class Module
def attribute desc, &block
if desc.is_a? Hash
name = desc.keys[0]
default = desc[name]
raise "Hash or block, not both" if block
else
name, default = desc, nil
end
ivar_name = "@#{name}"
module_eval do
define_method("#{name}?") {send(name) != nil}
define_method("__#{name}__ivarget") { instance_variable_get(ivar_name) }
define_method("__#{name}__ivarset") {|v| instance_variable_set(ivar_name, v) }
define_method(name) { send("#{name}=", block ? instance_eval(&block) : default) }
define_method("#{name}=") do |value|
(class << self; self; end).class_eval do
alias_method "#{name}", "__#{name}__ivarget"
alias_method "#{name}=", "__#{name}__ivarset"
end
instance_variable_set(ivar_name, value)
end
end
end
end
end

--------------090205020702090600080900--
 
G

George Ogata

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
 
S

Sander Land

...
define_method("#{arg}?"){|| !send(arg).nil?}
...
what about send(arg) =3D=3D 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 =3D 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)=09
end
} if val || blk
end
end
 
J

Jim Freeze

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 Freeze
 
M

Meador Inge

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
 
A

aurelianito

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
 
G

George Ogata

Sander Land said:
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?'.
 
P

Patrick Hurley

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

George Ogata

Patrick Hurley said:
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)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,969
Messages
2,570,161
Members
46,705
Latest member
Stefkari24

Latest Threads

Top