Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > Ruby > How to make generic #== method?

Reply
Thread Tools

How to make generic #== method?

 
 
Zakaria
Guest
Posts: n/a
 
      06-17-2004
Hi,

Currently I got many simple class which is just a simple record-like.
An example
-----------------------------------
class LoopTag # :nodoc:
attr_reader aram
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
http://www.velocityreviews.com/forums/(E-Mail Removed) Yahoo!: z4k4ri4
http://zakaria.is-a-geek.org
http://pemula.linux.or.id


 
Reply With Quote
 
 
 
 
Joel VanderWerf
Guest
Posts: n/a
 
      06-18-2004
Zakaria wrote:
> Hi,
>
> Currently I got many simple class which is just a simple record-like.
> An example
> -----------------------------------
> class LoopTag # :nodoc:
> attr_reader aram
> 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).


 
Reply With Quote
 
 
 
 
Zakaria
Guest
Posts: n/a
 
      06-18-2004
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 aram
> > 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
(E-Mail Removed) Yahoo!: z4k4ri4
http://zakaria.is-a-geek.org
http://pemula.linux.or.id


 
Reply With Quote
 
Gavin Sinclair
Guest
Posts: n/a
 
      06-18-2004
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




 
Reply With Quote
 
Robert Klemme
Guest
Posts: n/a
 
      06-18-2004

"Zakaria" <(E-Mail Removed)> schrieb im Newsbeitrag
news:(E-Mail Removed)...
> Hi,
>
> Currently I got many simple class which is just a simple record-like.
> An example
> -----------------------------------
> class LoopTag # :nodoc:
> attr_reader aram
> 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

 
Reply With Quote
 
Florian Gross
Guest
Posts: n/a
 
      06-18-2004
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
 
Reply With Quote
 
Paul Brannan
Guest
Posts: n/a
 
      06-18-2004
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



 
Reply With Quote
 
Gavin Sinclair
Guest
Posts: n/a
 
      06-18-2004
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



 
Reply With Quote
 
Zakaria
Guest
Posts: n/a
 
      06-18-2004
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
(E-Mail Removed) Yahoo!: z4k4ri4
http://zakaria.is-a-geek.org
http://pemula.linux.or.id


 
Reply With Quote
 
Tom Copeland
Guest
Posts: n/a
 
      06-18-2004
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



 
Reply With Quote
 
 
 
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
Re: How include a large array? Edward A. Falk C Programming 1 04-04-2013 08:07 PM
not just generic type programming,but also parallism generic syntaxprogramming?? minlearn C++ 2 03-13-2009 05:17 PM
generic interfaces with generic methods Murat Tasan Java 1 02-03-2009 12:17 PM
Generic class in a non generic class nramnath@gmail.com Java 2 07-04-2006 07:24 AM
The best way to make a generic report page using a web service indicated in a xml file. Pablo Gutierrez ASP .Net 0 10-27-2003 11:03 PM



Advertisments