Reflection, observ(er|able) instance vars?

Problem with the == method you defined is that hash equality != object
equality - especially for your hashing method.

This module will provide you with the hash and eql? methods (alias those
to other methods as appropriate). The hash function is cached, and reset
whenever a var= method is called (make sure you don't go modifying
hashed vars directly).

This is probably not the only/best way to do this, but it made sense to
me:

module FastHashEquals
    def attr_hashing( *arr )
        attr_reader *arr
        hashers = class_eval "@@__hashers ||= "
        hashers.concat(arr)
        arr.each do |var|
            define_method(var) {
                instance_variable_get("@#{var}")
            }
            define_method("#{var}=") {|obj|
                instance_variable_set("@#{var}", obj)
                instance_variable_set(:@__equals_hash, nil)
            }
            define_method('eql?') { |other|
                hash === other.hash && hashers.all? { |x|
                    send(x) == other.send(x)
                }
            }
            define_method('hash') {
                @__equals_hash ||= hashers.inject(0) { |s,x|
                    s + instance_variable_get("@#{x}").hash
                }
            }
        end
    end
end

To use:

class HashTest
  extend FastHashEquals
  attr_hashing :one, :two, :three
end

···

-----Original Message-----
From: Trans [mailto:transfire@gmail.com]
Sent: Friday, 21 October 2005 9:32 AM
To: ruby-talk ML
Subject: Re: Reflection, observ(er|able) instance vars?

Use Module, or better yet your own mixin. And just do the
class eval yourself w/o metaid, it's pretty easy

    def ==( other )
      hash == other.hash
    end
  end

#####################################################################################
This email has been scanned by MailMarshal, an email content filter.
#####################################################################################

Problem with the == method you defined is that hash equality != object
equality - especially for your hashing method.

Yes, I'd spotted that and was going to take that into account.
Thanks

This module will provide you with the hash and eql? methods (alias those
to other methods as appropriate). The hash function is cached, and reset
whenever a var= method is called (make sure you don't go modifying
hashed vars directly).

This is probably not the only/best way to do this, but it made sense to
me:

module FastHashEquals
    def attr_hashing( *arr )
        attr_reader *arr
        hashers = class_eval "@@__hashers ||= "
        hashers.concat(arr)
        arr.each do |var|
            define_method(var) {
                instance_variable_get("@#{var}")
            }
            define_method("#{var}=") {|obj|
                instance_variable_set("@#{var}", obj)
                instance_variable_set(:@__equals_hash, nil)
            }
            define_method('eql?') { |other|
                hash === other.hash && hashers.all? { |x|
                    send(x) == other.send(x)
                }
            }
            define_method('hash') {
                @__equals_hash ||= hashers.inject(0) { |s,x|
                    s + instance_variable_get("@#{x}").hash
                }
            }
        end
    end
end

To use:

class HashTest
  extend FastHashEquals
  attr_hashing :one, :two, :three
end

This "delivers us from eval" :slight_smile: but does define_method offer
anything else over the class_eval %{...} approach? Maybe [only :-)]
speed?

        Thank you,
        Hugh

···

On Fri, 21 Oct 2005, Daniel Sheppard wrote:

Yes, but not perhaps what you were expecting (2 files below):

# file 1: compare def vs block method call overhead
require 'benchmark'
include Benchmark

eval %[
def def_method(x)
  x
end
]

Object.send(:define_method, :defined_method) { |y|
  y
}

block_call = proc {|z| z }

BM = 100000
bm(20) {|x|
  x.report("def") { BM.times { def_method(1) }}
  x.report("define_method") { BM.times { defined_method(1) }}
  x.report("block_call") { BM.times { block_call[1] }}
  }
__END__
                          user system total real
def 0.070000 0.000000 0.070000 ( 0.070000)
define_method 0.240000 0.000000 0.240000 ( 0.251000)
block_call 0.250000 0.000000 0.250000 ( 0.250000)

# file 2: compare cost of eval vs define method

require 'benchmark'
include Benchmark

BM = 100000
bm(20) {|x|
  x.report("eval def") { BM.times { eval "def def_method(x) x end" }}
  x.report("define_method") { BM.times { Object.send(:define_method,
:defined_method) { |y| y } }}
  x.report("block_call") { BM.times { block_call = proc {|z| z } }}
  }
__END__
                          user system total real
eval def 2.744000 0.000000 2.744000 ( 2.744000)
define_method 3.585000 0.040000 3.625000 ( 3.686000)
block_call 1.191000 0.050000 1.241000 ( 1.252000)

Regards,

Sean

(P.S. This is on WinXP SP2 Dell D600 laptop with 1.6GHz Pentium + 1GB RAM)

···

On 10/21/05, Hugh Sasse <hgs@dmu.ac.uk> wrote:

This "delivers us from eval" :slight_smile: but does define_method offer
anything else over the class_eval %{...} approach? Maybe [only :-)]
speed?

Hugh Sasse wrote:

This "delivers us from eval" :slight_smile: but does define_method offer
anything else over the class_eval %{...} approach? Maybe [only :-)]
speed?

Daniel makes some fair points, but there's nothing wrong with using
class_eval here. In fact with eval it's more concise the resulting code
will be faster.

T.