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.
912c61d9da47754de7039f4271334a9f?d=identicon&s=25 MenTaLguY (Guest)
on 2006-02-19 19:54
(Received via mailing list)
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
644f884e05d9aaf6a076720a572874a2?d=identicon&s=25 Greg Millam (Guest)
on 2006-02-19 20:58
(Received via mailing list)
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
644f884e05d9aaf6a076720a572874a2?d=identicon&s=25 Greg Millam (Guest)
on 2006-02-20 16:42
(Received via mailing list)
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?
;)

- Greg
573b9499030e1ccb867ef80f0ff1ac49?d=identicon&s=25 Justin Bailey (Guest)
on 2006-02-20 17:47
(Received via mailing list)
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
97cbca14d17274370cce501bbea7980a?d=identicon&s=25 Mike Harris (Guest)
on 2006-02-21 15:35
(Received via mailing list)
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
97cbca14d17274370cce501bbea7980a?d=identicon&s=25 Mike Harris (Guest)
on 2006-02-21 16:39
(Received via mailing list)
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
This topic is locked and can not be replied to.