Metakoans.rb (#67)


#1

I’ve got two.

Here’s the first one I did:

class Module
def attribute( name, &default_block )
name, default_value = name.to_a.first if name.is_a? Hash
default_block ||= proc { default_value }

 ivar = "@#{ name }".to_sym

 define_method name do
   value = instance_variable_get ivar
   unless value or instance_variables.include? ivar.to_s
     instance_variable_set ivar, instance_eval( &default_block )
   else
     value
   end
 end

 define_method "#{ name }=" do |value|
   instance_variable_set ivar, value
 end

 alias_method "#{ name }?", name

end
end

23 lines.

Then here’s the second one I did, after Ara clarified that I needn’t use
instance variables:

class Module
def attribute( name, &default_block )
name, default_value = name.to_a.first if name.is_a? Hash
default_block ||= proc { default_value }

 name = name.to_sym

 define_method( "__default__#{ name }", &default_block )

 define_method( :__attributes__ ) do
   @__attributes__ ||= Hash.new { |h, k| h[k] = send "__default__#{ 

k }" }
end

 define_method( name ) { __attributes__[name] }
 define_method( "#{ name }=" ) { |value| __attributes__[name] = 

value }
alias_method “#{ name }?”, name
end
end

18 lines.

Actually, this last one can be mildly golfed down to 13 lines, at the
cost of getting a little ugly and a little slow:

class Module
def attribute( name, &default_block )
name, default_value = name.to_a.first if name.is_a? Hash
default_block ||= proc { default_value }
define_method( “default#{ name }”, &default_block )
define_method( :attributes ) do
@attributes ||= Hash.new { |h, k| h[k] = send “default#{
k }” }
end
define_method( name ) { attributes[name] }
define_method( “#{ name }=” ) { |value| attributes[name] =
value }
alias_method “#{ name }?”, name
end
end

Anyway, that’s all. Both these versions only do one attribute at a
time, since that’s all the tests asked for. Plus I don’t think the
block would make sense for multiple attributes.

And Ara? This Quiz rocked so much. Thank you!

-mental


#2

Here’s my solution: Took me about 15 minutes to solve the koans. I agree
with everyone else: Great quiz =). Continuous fiddling with it made me
late for a date, and when I tried explaining why, she just stared at me.
“Meta cones? What’s that?” Ah well!

My koan solution, 9 lines:

def one_attribute(arg,&b)
name = (arg.is_a?(Hash) ? arg.keys[0] : arg).to_s
p = Hash.new( arg.is_a?(Hash) ? arg[name] : nil )
fun = lambda { |*args|
p[self] = *args unless args.empty?
(p.include?(self) or !b) ? p[self] : instance_eval(&b)
}
[’’,’?’,’=’].each { |ch| define_method(name+ch,&fun) }
end

And to make it accept multiple arguments to work with the benchmark
suites (10 lines):

def attribute(arg,&b)
attribute(*rest,&b) if rest.any?
name = (arg.is_a?(Hash) ? arg.keys[0] : arg).to_s
p = Hash.new( arg.is_a?(Hash) ? arg[name] : nil )
fun = lambda { |*args|
p[self] = *args unless args.empty?
(p.include?(self) or !b) ? p[self] : instance_eval(&b)
}
[’’,’?’,’=’].each { |ch| define_method(name+ch,&fun) }
end

With comments:

def attribute(arg,&b)

Allow multiple attributes to be set and defined.

attribute(*rest,&b) if rest.any?

The name of the attribute.

name = (arg.is_a?(Hash) ? arg.keys[0] : arg).to_s

p holds all the attributes for each object.

This is wasteful since in real use, this would cause

Each object that’s set an attribute to be kept in

memory.

p = Hash.new( arg.is_a?(Hash) ? arg[name] : nil )

The only method I define: It takes 1 or more arguments.

If it’s given an argument, it assigns. In all cases,

It returns.

fun = lambda { |*args|
# Assign if necessary.
p[self] = *args unless args.empty?
# If it’s been assigned, or there’s no block, return
# Its saved value (Or Hash’s default)
(p.include?(self) or !b) ? p[self] : instance_eval(&b)
}

Assign that method to all 3 methods we need.

[’’,’?’,’=’].each { |ch| define_method(name+ch,&fun) }
end

More quizzes like this would be much appreciated. It rocked.

Ara++

  • Greg

#3

As with everyone, it seems, some more twiddling and playing with this
yielded me a better, faster, and smaller solution.

This also beats my prior solution in that a? returns only true or false,
and variables are kept with the object rather than in a hash with the
object as a key. The reader methods also won’t take an argument now.
(They did before.)

9 lines with a little golfing: (8 if you ditch the line for multiple
definitions, which isn’t needed for passing the koans).

It’s pretty much the same as most of the other solutions out there, now.

def attribute(arg,*rest,&b)
attribute(*rest,&b) if rest.any? # Allow multiple definitions.
n = (arg.is_a?(Hash) ? arg.keys[0] : arg).to_s
b ||= lambda { arg.is_a?(Hash) ? arg[n] : nil }
attr_writer n
define_method(n) { instance_variables.include?(’@’+n) ?
instance_variable_get(’@’+n) : instance_eval(&b) }
define_method(n+’?’) { ! [nil,false].include?(send(n)) }
end

Blast it, Ara, how do I stop myself from doing more and more tweaking?
:wink:

  • Greg

#4

I came up with a solution that involved creating a default initializer
method for each attribute set. Great quiz!

class Object
def attribute(arg, &block)
# Determine if we have an initial value for the attribute or not,
and
put
# them all into a common form.
if arg.is_a?(Hash)
props = arg.collect { |k, v| k }
else
# Note this sets the initial value to nil, which is a no-op below,
if
# no block was specified.
props = [arg]
arg = { arg => block }
end

props.each do |p|
  instance_var, init_meth = "@#{p}".to_sym, "#{p}_init".to_sym

  if (val = arg[p])
    # set up initializer methods for block or given value. Note a
    # method is created for each attribute given that has a value

associated
self.instance_eval do
if val.is_a?(Proc)
define_method init_meth, val
else
define_method(init_meth) { val }
end
# set visibility
private init_meth
end
end

  # define attribute accessor methods
  class_eval do
    attr_writer p.to_sym

    # for first time access, look to appropriate init method, if any 

and
# get value. In either case, the instance_variable will be
defined
after
# this method if it wasn’t before.
define_method(p.to_sym) do
unless x = instance_variable_get(instance_var) || val.nil?
instance_variable_set(instance_var, x =
self.send(init_meth))
end
x
end

    # Define query accessor. Only returns true if the instance 

variable
is defined,
# regardless of its value.
define_method("#{p}?") do
! instance_variable_get(instance_var).nil?
end
end
end
end
end


#5

Here’s a better version of my solution. Obviously these modules would
have to be included in Object to work. The names should be self
explainitory. All lines are less than 80 chars.

module Attribute20PercentGolfed
def attribute(arg, &b)
return attribute(arg.keys[0]) { arg.values[0] } if arg.class == Hash
define_method(arg) do
init = block_given? && !instance_eval(“defined? “+”@”+arg)
init ? send(arg+"=",instance_eval(&b)) :
instance_variable_get("@"+arg)
end
define_method(arg+"?") { !!send(arg) }
attr_writer(arg)
end
end

module Attribute75PercentGolfed; def attribute(a, &b)
return attribute(a.keys[0]) { a.values[0] } if a.class == Hash
define_method(a) { block_given? && !instance_eval(“defined? “+”@”+a) ?
send(a+"=",instance_eval(&b)):instance_variable_get("@"+a) }
define_method(a+"?") { !!send(a) } && attr_writer(a); end; end


#6

The only interesting thing I have to say here that hasn’t been said is
that this test seems like a variation of Pair Programming, where one
person writes the tests and the other writes the code. No better way to
ensure you only write code to make a failing test pass than to know
nothing about the problem space except the most recent test failure.

class Object
def attribute(arg, &block)
name = (arg.class == Hash ? arg.keys[0] : arg)
define_method(name) do
first_access_action(arg,&block) unless instance_eval(“defined? " +
“@” + name)
instance_variable_get “@” + name
end
attr_writer name
alias_method name+”?",name
end

def first_access_action(arg,&block)
name = (arg.class == Hash ? arg.keys[0] : arg)
send(name+"=",instance_eval(&block)) if block_given?
send(name+"=",arg.values[0]) if arg.class == Hash
end
end