@hgs: "I have an object for which equality tests take a long time, and I
have lots of others to compare it with.  It is
an instance variable of my class, and it is also accessble by
attr_accessor.  I'd like to hold a hash (number not Hash.new()) for
this object so I can compare it quickly with another.  How do I detect that
it has changed so that the hash gets updated correctly?  Does attr_accessor
construct code that leaves the right hooks in to detect this, or
must I write my own?  Should attr_accessor do that if it doesn't?
Can I trap this by interposing in freeze tests, somehow?
If this is a code smell, how should I handle frequently queried,
slow-to-test, but changing instance variables?  Make them all observables
and use update()? (That sounds expensive.)
        Thank you
        Hugh"
    @hgs: "<quote>
        [...]
If only I could add it to object.  I've tried, but _Why's metastuff
confuses me just sufficiently to stop me making the transition.
Given  metaid.rb thusly:
<quote>
#!/usr/local/bin/ruby -w
#
# Due to Whytheluckystiff.
# <url>
# Reformatted to work around bugs in vim indent.
#
#
class Object
  # The hidden singleton lurks behind everyone
  def metaclass
    class << self
      self
    end
  end
  def meta_eval &blk
    metaclass.instance_eval &blk
  end
  # Adds methods to a metaclass
  def meta_def name, &blk
    meta_eval { define_method name, &blk }
  end
  # Defines an instance method within a class
  def class_def name, &blk
    class_eval { define_method name, &blk }
  end
end
</quote>
and  hashing.rb thusly:
<quote>
#!/usr/local/bin/ruby -w 
#
#  Make a class keep its hash up to date by using 
#  accessor methods that call hash on update.
#
#  Hugh Sasse
#  created: 20-OCT-2005  
#
#
require 'metaid'   # See <url>
 class Object
   def self.attr_hashing( *arr )
     attr_reader *arr
     arr.each do |var|
       meta_def var do |val|
         @var 
       end
       vareq = (var.to_s + "=")
       meta_def vareq do |val|
         instance_variable_set( "@#{var}", val )
         self.hash
       end
     end
=begin
     class_def :initialize do
       self.class.traits.each do |k,v|
         instance_variable_set( "@#{k}", v )
       end
     end
=end
   end
 end
</quote>
which I've produced from some badly aimed kicks at Dwemthy's Array,
I can't actually get the results I'd like.
<quote>
#!/usr/local/bin/ruby -w
#
# Test out the hashing prog.
#
require 'hashing'
class Wombat
  attr_hashing :wom, :bat
  attr_reader :thehash
  def initialize
    @wom = "wom"
    @bat = "bat"
    @thehash = hash
  end
  def hash
    puts "hash called"
    @thehash = (@wom.to_s + @bat.to_s).hash
  end
  def to_s
    @wom.inspect + @bat.inspect
  end
end
x = Wombat.new  # produce the hash message
puts x
puts x.wom
puts x.bat
puts x.methods.sort.join(', ')
x.wom = "WOM"   # produce the hash message
puts x.wom
x.bat = "BAT"   # produce the hash message
puts x.bat
</quote>
Does anyone feel inclined to help me out?  :-)
        Thank you,
        Hugh"
    @daniels: "Problem with the =3D=3D method you defined is that hash equality !=3D obj=
ect
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=3D method is called (make sure you don't go modifying
hashed vars directly).=20
This is probably not the only/best way to do this, but it made sense to
me:
module FastHashEquals
=20   def attr_hashing( *arr )
=20       attr_reader *arr
=20       hashers =3D class_eval "@@__hashers ||=3D []"
=20       hashers.concat(arr)
=20       arr.each do |var|
=20           define_method(var) {
=20               instance_variable_get("@#{var}")
=20           }
=20           define_method("#{var}=3D") {|obj|
=20               instance_variable_set("@#{var}", obj)
=20               instance_variable_set(:@__equals_hash, nil)
=20           }
=20           define_method('eql?') { |other|
=20               hash =3D=3D=3D other.hash && hashers.all? { |x|
=20                   send(x) =3D=3D other.send(x)
=20               }               =20
=20           }
=20           define_method('hash') {
=20               @__equals_hash ||=3D hashers.inject(0) { |s,x|
=20                   s + instance_variable_get("@#{x}").hash
=20               }             =20
=20           }
=20       end
=20   end
end
To use:
class HashTest
=09extend FastHashEquals
=09attr_hashing :one, :two, :three
end
<quote>
#########################################################################=
############
This email has been scanned by MailMarshal, an email content filter.
#########################################################################=
############"
    @hgs: "<quote>
That's very nice! :-)
Yes, that's much simpler than the knots I'd got myself into.  Thank
you.   That will help considerably.
        Hugh"
    @hgs: "<quote>
Yes, I'd spotted that and was going to take that into account.
Thanks
This "delivers us from eval" :-) but does define_method offer
anything else over the class_eval %{...} approach?  Maybe [only :-)]
speed?
        Thank you,
        Hugh"
    @seanohalpin: "<quote>
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 =3D proc {|z| z }
BM =3D 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 =3D 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 =3D 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)"
    @daniels: "I generally use define_method only because it works better with the
syntax highlighting of SciTE, and also make the code look a little
unnatural... you also can't pass in real objects to be used by the
method, only things that are stringable.
class_eval %{
=09def #{method_name}(*args)
=09	stuff
=09end
}
gets all screwed up (the #{} string is treated as a comment), but=20
define_method :method_name { |*args|
=09stuff
}
works fine. If I was trying to make it run fast, I'd probably be using
class_eval, not define_method.
Evals can be slow, but you're only doing the eval once when it's a
class_eval, and then it's just like any other method, whereas with
define_method you're calling off to the proc object every time, which is
slower than a method call for each and every call.
<quote>
#########################################################################=
############
This email has been scanned by MailMarshal, an email content filter.
#########################################################################=
############"
    @hgs: "<quote>
        [...]
I couldn't get the class_eval form to work.  
I don't know by how much.  Anyway, this will be much faster than
doing a full compare each time, and faster than doing a hash each
time, so I don't really need to milk the last drop.
Here is what I ended up with in the end.  It does what I want.
If anyone feels like improving on it, you are welcome to do so.
        Thank you
        Hugh
#!/usr/local/bin/ruby -w
#
# Ruby-Talk:161789  from: "Daniel Sheppard" <email_address>
# Provides hashing for an object based on instance vars, and handles
# changes to those vars gracefully.
# The thread begins with Ruby-Talk:161631.
#
# Modified by Hugh Sasse to move it into class Class so attr_hashing
# works like the other attr methods.  Also added code so that
# freezing works correctly: Hash works faster with frozen objects.
#
# require 'fasthashequals'
# class Example
#   attr_hashing :this, :that, :other
#
# end
#
# For getting an array back as Ruby code.
class Array
  def display
    return "[ "+ map{|x|
      x.inspect
    }.join(', ') + " ]"
  end
end
class Class
    # attr_hashing needs to be a method of class to be useable as
    # the other attr methods are.  This method sets up the
    # informtion and methods needed to create the hash from those
    # instance variables.
    def attr_hashing(*arr)
        stararr = arr.flatten
        # puts "stararr is #{stararr.inspect}"
        (stararr).each do |sym|
          attr_reader sym
        end
        hashers = class_eval "@@__hashers ||= []"
        hashers.concat(stararr)
        hashers.uniq!
        # puts "@@__hashers = " + hashers.display
        class_eval "@@__hashers = " + hashers.display
        stararr.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') {
              # puts "in hash, self is #{self.inspect}"
              @__equals_hash ||= hashers.inject(0) do |s,x|
                  s + instance_variable_get("@#{x}").hash
              end              
            }
            # Once we freeze an object we can't update its hash again
            define_method('freeze') {
              hash
              super
            }
        end
    end
end"
{'source': 'USENET', 'other_information': '{"reply_count": 7, "forum_name": "comp.lang.ruby.20141021.mbox"}', 'file': 'data/output/usenet-comp.lang.parquet'}