Re: Hash Surprises with Fixnum, #hash, and #eql?



On 04/07/11 19:19, Robert Klemme wrote:
On Thu, Apr 7, 2011 at 6:05 AM, Clifford Heath<no@xxxxxxxxxxxxxxx> wrote:
I have a class which delegates for Integer, and wants to behave as much
like a real Integer as possible (except for being able to be subclassed).
There's still a lot missing for a number replacement. Please see
http://blog.rubybestpractices.com/posts/rklemme/019-Complete_Numeric_Class.html

Yes, you wrote that about the time we discussed it last time.

I also doubt whether it is a good idea to allow for subclassing of an
integer like class. What use case do you have in mind which would
make this necessary?

What's wrong with the case you use in that blog post? But as it turns out,
I'm implementing a fact-based modeling DSL, where it's sensible to have
classes like "AgeInYears" being a subclass of an integer like class.
The formalism for this comes directly from sorted first-order logic,
which makes a good deal more sense than the broken O-O paradigm discussed
elsewhere in this thread.

I suspect that you "doubt it is a good idea" only because Ruby's object
model for numbers is inconsistent, and you're defensive about that. Not
because Ruby 2.0 shouldn't move in the direction of fixing it, where
possible. (BTW, I tried to join Ruby Core to discuss this, but all
possible means of subscription are silently failing me).

Note that I'm not actually subclassing any core integer class. I'm just
defining a new base class "Int" which contains an integer, and so far
as is possible, acts like one, including being found in a Hash using a
Fixnum/Bignum key.

If Fixnum and Bignum can act like Integer subclasses, why can't my class?

In particular, the Hash implementations work (and break!) differently in
MRI, Rubinius and JRuby. It's documented to use only #hash and #eql?,
but that's not always true (sometimes these have hard-wired optimsations).
When you violate contracts you cannot expect code to work properly.

I have not violated that (unstated!) contract. Read again; I redefine
Fixnum#eql? as self.orig_eql?(i.to_i) - the to_i makes it symmetrical.
(Debate the wisdom if you wish, it's just for demonstration purposes.)

However the Ruby interpreters do not honor that. In short, *all three*
mentioned Ruby interpreters violate the Hash contract, which states that
hash and eql? are used for Hash lookups. Not just sometimes, but all the
time, including for integers.

MRI uses a Fixnum as its own hash value, even if you've monkey-patched a
hash method into Fixnum. This optimisation should not be always-on. Instead,
Ruby should detect when Fixnum has been patched, and bypass the optimisation.
That would require a single test and branch, with insignificant impact on
performance. MRI does however use a monkey-patched Fixnum#eql? method.

Rubinius does the opposite. It calls a patched Fixnum#hash, but not Fixnum#eql?

JRuby calls neither.

The Ruby interpreters should behave the way the Hash documentation says they do.

The Ruby documentation should explicitly state that eql? must be defined
symmetrically, or should require that the Hash implementation uses it only
in a known direction or both.

The Hash documentation does not say whether #eql? will be called only on
items in the hash, or only on keys being used to probe the hash. It should
be one or the other, since a.eql?(b) might not always mean b.eql?(a).

But that is the contract as far as I can see.

That's not documented anywhere I can see. Certainly not in TRPL, see sections
3.4.2 on page 68, and section 3.8.5.3 page 77. It makes sense, but it's not
stated.

Having different
results for both violates the equivalence relation which means all
bets are off.

No. I can fix the asymmetry. I can't make the interpreters honor that fix.

You'll see that the behaviour is very unpredictable.
Yes, because of your violation of the contract.

No. Because the Ruby interpreters don't honor the Hash contract.

Please try to read more carefully.

Clifford Heath.
.



Relevant Pages

  • Re: Hash Surprises with Fixnum, #hash, and #eql?
    ... That works because Fixnum ... Hash[] etc. to get the behavior you desire. ... many users of Ruby instead of just going ahead and also ... and yet JRuby's Hash optimises both eql? ...
    (comp.lang.ruby)
  • Re: whats String.hash
    ... What's the meanings of String.hash method? ... str.hash => fixnum ... From Ruby 1.8 ... Return a hash based on the string's length and content. ...
    (comp.lang.ruby)
  • Re: whats String.hash
    ... Generates a +Fixnum+ hash value for this object. ... I'm new to Ruby and have been wondering what +...+ is. ... emphasis in some markup language? ...
    (comp.lang.ruby)
  • Re: Not So Random (#173)
    ... Ruby" way, maybe;) Then, along the way, i discovered a really strange ... def getID ... large outputs could be to hash a counter, I realized, so I wrote another ... I was taught that a hash function should strive to distribute the inputs ...
    (comp.lang.ruby)
  • Re: A couple of questions regarding class design
    ... Do I return nil or some other result? ... I thought that there might be a way to return a value signifying an invalid entry without having to worry about exception handling. ... Haven't got that far in Ruby yet. ... You also need #hash to make instances of your class behave properly as Hash keys. ...
    (comp.lang.ruby)