[Ruby] eql? and hash on Sets

Aaron Patterson aaron at tenderlovemaking.com
Sun Nov 25 11:27:35 PST 2007


On Sat, Nov 24, 2007 at 06:09:10PM -0800, Ryan Davis wrote:
> 
> On Nov 24, 2007, at 10:35 , Aaron Patterson wrote:
> 
> > Given the behavior of Array#hash and Array#eql?, what would you expect
> > Set#hash and Set#eql? to do?  I expected Set to behave the same way as
> > Array, but it does not.
> 
> I would expect set to behave more like Hash than Array... or at least,  
> I would expect it to act like some genetic freak merge of the two.
> 
> > However, I was surprised to find that Set
> > implements eql? and hash.  That leads me to believe that the original
> > intent was for Set to behave like Array in that department, and that
> > this is a bug.
> 
> Why would the implementation of #eql? and #hash lead you to believe  
> that it'd act like an Array? I don't see how that follows.

I guess it didn't really.  Set just seemed to quack like an Array most
of the time.  And since .eql? and .hash were implemented, I expected
them to sort of act like Array as well.

> 
> That said, it seems wrong that Set#eql? is defined by Hash#eql? (which  
> defaults to Object#eql? and is therefor the same as Object#equal?-- 
> object identity). So yes, it seems like a bug, sorta.

Agreed.

> 
> > As far as I can tell, no two Sets can eql? each other.
> 
> No two different sets...
> 
> > % irb
> > >> require 'set'
> > => true
> > >> s = Set.new [1, 2, 3]
> > => #<Set: {1, 2, 3}>
> > >> s2 = s
> > => #<Set: {1, 2, 3}>
> > >> s.eql? s2
> > => true
> 
> The real question is, how exactly are you abusing sets that makes you  
> poke into this area? Using them as hash keys?

I'm using them in CSSPool.  I need to keep track of properties for each
rule.  Each rule has a selector and many properties.  I don't really
care what order the properties are in, but I do care that they are
unique.  So Set seemed appropriate.  Then I wanted to reduce my CSS, and
group all rules by property.  So if many rules had the same properties,
I could reduce the size of the CSS.

Here's a short example:

  h1 {
    background: red;
  }

  div {
    background: red;
  }

Could be re-written as:

  h1, div {
    background: red;
  }

Here is what I made Set do:

  class Set
    def eql?(o)
      return false unless o.is_a?(Set)
      @hash.keys.sort_by { |x| x.hash }.
        eql?(o.instance_eval{@hash}.keys.sort_by { |x| x.hash })
    end

    def hash
      @hash.keys.sort_by { |x| x.hash }.hash
    end
  end

I don't particularly care what order the set is in, so I sorted by hash
and eql?'d on that.  It could probably be sped up by comparing lengths
of the keys, but I didn't really care.....

-- 
Aaron Patterson
http://tenderlovemaking.com/


More information about the Ruby mailing list