Velocity Reviews

Velocity Reviews (http://www.velocityreviews.com/forums/index.php)
-   Ruby (http://www.velocityreviews.com/forums/f66-ruby.html)
-   -   How to make generic #== method? (http://www.velocityreviews.com/forums/t815205-how-to-make-generic-method.html)

Zakaria 06-17-2004 09:12 PM

How to make generic #== method?
 
Hi,

Currently I got many simple class which is just a simple record-like.
An example
-----------------------------------
class LoopTag # :nodoc:
attr_reader :param
attr_reader :contents

def initialize(param)
@param, @contents = param, []
end

def ==(other)
return false unless other.class == self.class
return other.param == @param && other.contents == @contents
end
end
------------------------------------
and I got a couple more class like this.

Is there a way to make a generic #== or some Module that I can include ?

I surely need it to do assert_equal on unit test.
You know, I just waste a couple hour tracking bug that doesn't exists
because I forgot to implement #== :)

TIA,


-- Zakaria
z4k4ri4@bigfoot.com Yahoo!: z4k4ri4
http://zakaria.is-a-geek.org
http://pemula.linux.or.id



Joel VanderWerf 06-18-2004 01:15 AM

Re: How to make generic #== method?
 
Zakaria wrote:
> Hi,
>
> Currently I got many simple class which is just a simple record-like.
> An example
> -----------------------------------
> class LoopTag # :nodoc:
> attr_reader :param
> attr_reader :contents
>
> def initialize(param)
> @param, @contents = param, []
> end
>
> def ==(other)
> return false unless other.class == self.class
> return other.param == @param && other.contents == @contents
> end
> end
> ------------------------------------
> and I got a couple more class like this.
>
> Is there a way to make a generic #== or some Module that I can include ?


One way I've done it is this:

module ContentEquality
def hash
content.hash
end

def eql?(other)
content.eql? other.content
end

def ==(other)
# self.class == other.class and # optional
content == other.content
end
end

Just include the module and define a content method that returns
something like and array of objects that are significant for comparison
purposes:

class LoopTag
include ContentEquality

def content
[@param, @contents]
end
end

Another way might be to iterate over a list of instance variables (or
all of 'em, if that's what you want).



Zakaria 06-18-2004 06:37 AM

Re: How to make generic #== method?
 
On Fri, Jun 18, 2004 at 10:15:44AM +0900, Joel VanderWerf wrote:
> Zakaria wrote:
> >Hi,
> >
> >Currently I got many simple class which is just a simple record-like.
> >An example
> >-----------------------------------
> >class LoopTag # :nodoc:
> > attr_reader :param
> > attr_reader :contents
> >
> > def initialize(param)
> > @param, @contents = param, []
> > end
> >
> > def ==(other)
> > return false unless other.class == self.class
> > return other.param == @param && other.contents == @contents
> > end
> >end
> >------------------------------------
> >and I got a couple more class like this.


> >Is there a way to make a generic #== or some Module that I can include ?


> One way I've done it is this:


> module ContentEquality
> def hash
> content.hash
> end


> def eql?(other)
> content.eql? other.content
> end


> def ==(other)
> # self.class == other.class and # optional
> content == other.content
> end
> end


> Just include the module and define a content method that returns
> something like and array of objects that are significant for comparison
> purposes:


> class LoopTag
> include ContentEquality


> def content
> [@param, @contents]
> end
> end


> Another way might be to iterate over a list of instance variables (or
> all of 'em, if that's what you want).


Thank you Joel for the answer.
This is what I ended up with
----------------------------------------------------------------------------
module AttrsEquality
def ==(other)
self.class === other && attrs == other.attrs
end

def attrs
res = {}
instance_variables.each {|n| res[n.intern] = instance_variable_get(n) }
res
end
end
----------------------------------------------------------------------------

with unit-test
----------------------------------------------------------------------------
class TC_AttrsEquality < Test::Unit::TestCase
class T1
include AttrsEquality
def initialize(a, b, c, d)
@a, @b, @d, @c = a, b, d, c
end
end

def test_attrs
t = T1.new(1, 'ab', ['some', 4], {'x' => 1, 3 => 'y'})
x = {:@a => 1, :@b => 'ab', :@c => ['some', 4],
:@d => {'x' => 1, 3 => 'y'}}
assert_equal(x, t.attrs)
end

def test_equal
t1 = T1.new(1, 'ab', ['some', 4], {'x' => 1, 3 => 'y'})
t2 = T1.new(1, 'ab', ['some', 4], {'x' => 1, 3 => 'y'})
assert(t1 == t2, 'equal')
end
end
----------------------------------------------------------------------------

I remove the eql? and hash method because
1) I don't need it
2) It doesn't work if the class has hash attribute because
{'a' => 1}.hash != {'a' => 1}.hash
3) I'm not really understand the functionality to test it

Could someone enlighten me why Hash#hash doesn't result the same
and where .hash and .eql? used ?

PS: Is there any place where I could post this snippet,
so others could use it?

Wassallam,


-- Zakaria
z4k4ri4@bigfoot.com Yahoo!: z4k4ri4
http://zakaria.is-a-geek.org
http://pemula.linux.or.id



Gavin Sinclair 06-18-2004 07:06 AM

Re: How to make generic #== method?
 
Zakaria wrote:
>
> I remove the eql? and hash method because
> 1) I don't need it


You probably need hash, and should define it (see below). About eql?, I'm
under the understanding that eql? and == should always return the same
thing.

> 2) It doesn't work if the class has hash attribute because
> {'a' => 1}.hash != {'a' => 1}.hash


Now that is weird. I've a faint recollection of it being mentioned on
ruby-talk before though, so perhaps its not a bug.

> 3) I'm not really understand the functionality to test it
>
> Could someone enlighten me why Hash#hash doesn't result the same
> and where .hash and .eql? used ?


#hash is used to generate a hash code, which is used to put an object into
a hash (i.e. a Hash object). If two objects have the same #hash value,
then a Hash thinks they're probably the same, but it's not guaranteed, so
it double-checks with #eql?. However, if two objects have *different*
#hash values, then a Hash considers them to be definitely different.

Bottom line, if you (re)define #== in an object, you really really should
(re)define #hash as well, to make sure they are consistent.

> PS: Is there any place where I could post this snippet,
> so others could use it?


As it happens, I've already written a detailed consideration of Joel's
code for the not-yet-formally-announced project 'addlib', so that people
can reuse it without having to manage the code themselves.

Until then, however, you might take a look at the snippet repository on
RubyForge.

Joel's code is better, however. What yours adds is the implicit
definition of #contents (in Joel's terminology). So why not:

module AttrsEquality
include ContentEquality
def contents
res = {}
instance_variables.each {|n| res[n.intern] = instance_variable_get(n) }
res
end
end

Gavin





Robert Klemme 06-18-2004 07:31 AM

Re: How to make generic #== method?
 

"Zakaria" <zakaria@suarametro.com> schrieb im Newsbeitrag
news:20040617211611.GA4734@zakbox.local...
> Hi,
>
> Currently I got many simple class which is just a simple record-like.
> An example
> -----------------------------------
> class LoopTag # :nodoc:
> attr_reader :param
> attr_reader :contents
>
> def initialize(param)
> @param, @contents = param, []
> end
>
> def ==(other)
> return false unless other.class == self.class
> return other.param == @param && other.contents == @contents
> end
> end
> ------------------------------------
> and I got a couple more class like this.
>
> Is there a way to make a generic #== or some Module that I can include ?
>
> I surely need it to do assert_equal on unit test.
> You know, I just waste a couple hour tracking bug that doesn't exists
> because I forgot to implement #== :)


module MemberEquivalence
def ==(o)
instance_variables.each do |var|
return false unless instance_variable_get(var) ==
o.instance_variable_get(var)
end

true
end

def hash
h = 0

instance_variables.each do |var|
val = instance_variable_get(var)
h ^= val.hash unless val.nil?
end

h
end

alias :eql? :==
end

class Foo
include MemberEquivalence

attr_accessor :foo, :bar
end


>> f=Foo.new

=> #<Foo:0x10194988>
>> g=Foo.new

=> #<Foo:0x1018f918>
>> f == g

=> true
>> f.foo = "x"

=> "x"
>> f == g

=> false
>> f.foo = nil

=> nil
>> f == g

=> true
>> f.hash

=> 0
>> g.hash

=> 0
>> f.eql? g

=> true
>> h={ f => 1 }

=> {#<Foo:0x10194988 @foo=nil>=>1}
>> h[g]

=> 1

Regards

robert


Florian Gross 06-18-2004 02:26 PM

Re: How to make generic #== method?
 
Moin!

Joel VanderWerf wrote:

> module ContentEquality
> def hash
> content.hash
> end
>
> def eql?(other)
> content.eql? other.content
> end
>
> def ==(other)
> # self.class == other.class and # optional
> content == other.content
> end
> end


That's cool, I think I'm going to use it in the future! :)

> Another way might be to iterate over a list of instance variables (or
> all of 'em, if that's what you want).


Hm, maybe a default .content could be provided by the module; it could
look like this:

module ContentEquality
def content
instance_variables.map { |name| instance_variable_get(name) }
end
end

Regards,
Florian Gross

Paul Brannan 06-18-2004 02:49 PM

Re: How to make generic #== method?
 
On Fri, Jun 18, 2004 at 10:15:44AM +0900, Joel VanderWerf wrote:
> One way I've done it is this:
>
> module ContentEquality
> def hash
> content.hash
> end
>
> def eql?(other)
> content.eql? other.content
> end
>
> def ==(other)
> # self.class == other.class and # optional
> content == other.content
> end
> end


I like this, and since content is ordered, it's possible to write this
too:

module ContentEquality
def <=>(other)
content.each_with_index do |member, idx|
result = member <=> other.content[idx]
return result if result != 0
end
return 0
end

include Comparable
end

The only thing I don't like is that content doesn't carry any
information about what it holds, so it's entirely possible to
inadvertently compare apples and oranges.

Paul




Gavin Sinclair 06-18-2004 02:56 PM

Re: How to make generic #== method?
 
On Saturday, June 19, 2004, 12:28:21 AM, Florian wrote:

> Moin!


> Joel VanderWerf wrote:


>> module ContentEquality
>> def hash
>> content.hash
>> end
>>
>> def eql?(other)
>> content.eql? other.content
>> end
>>
>> def ==(other)
>> # self.class == other.class and # optional
>> content == other.content
>> end
>> end


> That's cool, I think I'm going to use it in the future! :)


>> Another way might be to iterate over a list of instance variables (or
>> all of 'em, if that's what you want).


> Hm, maybe a default .content could be provided by the module; it could
> look like this:


> module ContentEquality
> def content
> instance_variables.map { |name| instance_variable_get(name) }
> end
> end


Because this discards the instance variable names, some pathological
cases would give false positives. Better to make #content return a
hash instead.

Gavin




Zakaria 06-18-2004 04:16 PM

Re: How to make generic #== method?
 
On Fri, Jun 18, 2004 at 04:06:32PM +0900, Gavin Sinclair wrote:
> Zakaria wrote:


> > 2) It doesn't work if the class has hash attribute because
> > {'a' => 1}.hash != {'a' => 1}.hash


> Now that is weird. I've a faint recollection of it being mentioned on
> ruby-talk before though, so perhaps its not a bug.


Could someone explain why?

> #hash is used to generate a hash code, which is used to put an object into
> a hash (i.e. a Hash object). If two objects have the same #hash value,
> then a Hash thinks they're probably the same, but it's not guaranteed, so
> it double-checks with #eql?. However, if two objects have *different*
> #hash values, then a Hash considers them to be definitely different.


> Bottom line, if you (re)define #== in an object, you really really should
> (re)define #hash as well, to make sure they are consistent.


They only needed if I put the object as the hash key right?

> > PS: Is there any place where I could post this snippet,
> > so others could use it?


> As it happens, I've already written a detailed consideration of Joel's
> code for the not-yet-formally-announced project 'addlib', so that people
> can reuse it without having to manage the code themselves.


I hope I could contribute.

> Until then, however, you might take a look at the snippet repository on
> RubyForge.


URL? I know I'm lazy :)

> Joel's code is better, however. What yours adds is the implicit
> definition of #contents (in Joel's terminology). So why not:


I think Robert Klemme version is better and I believe it's faster.
And I also like the name, MemberEquivalence.
OK going to sleep now

> Gavin


Wassallam,


-- Zakaria
z4k4ri4@bigfoot.com Yahoo!: z4k4ri4
http://zakaria.is-a-geek.org
http://pemula.linux.or.id



Tom Copeland 06-18-2004 04:30 PM

Re: How to make generic #== method?
 
On Fri, 2004-06-18 at 12:16, Zakaria wrote:
> > Until then, however, you might take a look at the snippet repository on
> > RubyForge.

>
> URL? I know I'm lazy :)


Yonder:

http://rubyforge.org/snippet/browse.php?by=lang&lang=17

Yours,

Tom





All times are GMT. The time now is 01:36 AM.

Powered by vBulletin®. Copyright ©2000 - 2014, vBulletin Solutions, Inc.
SEO by vBSEO ©2010, Crawlability, Inc.