[/QUIZ] metakoans.rb (#67)

Discussion in 'Ruby' started by Luke Blanshard, Feb 19, 2006.

  1. --------------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--
    Luke Blanshard, Feb 19, 2006
    #1
    1. Advertising

  2. Luke Blanshard

    George Ogata Guest

    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
    George Ogata, Feb 19, 2006
    #2
    1. Advertising

  3. Luke Blanshard

    Sander Land Guest

    > On 2/19/06, George Ogata <> wrote:
    > ...
    > 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
    Sander Land, Feb 19, 2006
    #3
  4. Luke Blanshard

    Jim Freeze Guest

    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
    Jim Freeze, Feb 19, 2006
    #4
  5. Luke Blanshard

    Meador Inge Guest

    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
    Meador Inge, Feb 19, 2006
    #5
  6. Luke Blanshard

    aurelianito Guest

    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
    aurelianito, Feb 19, 2006
    #6
  7. Luke Blanshard

    George Ogata Guest

    "Sander Land" <> writes:

    >> On 2/19/06, George Ogata <> 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?'.
    George Ogata, Feb 20, 2006
    #7
  8. On 2/20/06, George Ogata <> 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.
    Patrick Hurley, Feb 20, 2006
    #8
  9. Luke Blanshard

    George Ogata Guest

    "Patrick Hurley" <> writes:

    > On 2/20/06, George Ogata <> 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)
    George Ogata, Feb 21, 2006
    #9
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Ruby Quiz

    [QUIZ] Animal Quiz (#15)

    Ruby Quiz, Jan 14, 2005, in forum: Ruby
    Replies:
    11
    Views:
    350
    James Edward Gray II
    Jan 18, 2005
  2. David Tran
    Replies:
    9
    Views:
    176
    David Tran
    Jan 21, 2005
  3. Ruby Quiz

    [QUIZ] 1-800-THE-QUIZ (#20)

    Ruby Quiz, Feb 18, 2005, in forum: Ruby
    Replies:
    15
    Views:
    314
    gabriele renzi
    Feb 24, 2005
  4. Florian Groß

    [SOLUTION] metakoans.rb (#67)

    Florian Groß, Feb 19, 2006, in forum: Ruby
    Replies:
    7
    Views:
    155
    Jeremy Hinegardner
    Feb 21, 2006
  5. Ari Brown

    [Solution][QUIZ] #67 metakoans.rb

    Ari Brown, Oct 6, 2007, in forum: Ruby
    Replies:
    0
    Views:
    71
    Ari Brown
    Oct 6, 2007
Loading...

Share This Page