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

C

Clifford Heath

Folk,

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).
It *mostly* works... but falls foul of Ruby's various hacks, errors, and
internal optimisations in the Fixnum and Hash classes.

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).

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).

Please peruse this code: <https://gist.github.com/906998>, try it on the
various Ruby versions, and also try it with the Fixnum monkey-patches
removed.

You'll see that the behaviour is very unpredictable.

Clifford Heath.
 
R

Robert Klemme

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

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?
It *mostly* works... but falls foul of Ruby's various hacks, errors, and
internal optimisations in the Fixnum and Hash classes.
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.
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. Having different
results for both violates the equivalence relation which means all
bets are off.
Please peruse this code: <https://gist.github.com/906998>, try it on the
various Ruby versions, and also try it with the Fixnum monkey-patches
removed.

You'll see that the behaviour is very unpredictable.

Yes, because of your violation of the contract. You have there a nice
demonstration why it is a bad idea most of the time to fiddle with
core class method implementations. They are used everywhere and you
cannot foresee the effects of changing their implementation on other
code.

Kind regards

robert
 
P

Phillip Gawlowski

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

Complex numbers come to mind:

3 - 2j [+|*] 45^(j * e * 44=B0).

Very different semantics for addition and multiplication of those than
for your normal space numbers, including conversion from Cartesian to
polar form. Bit of a textbook case for the benefits of inheritance and
function overloading. :p

It'd be better to have those be a sub-class of Float, though.

--=20
Phillip Gawlowski

Though the folk I have met,
(Ah, how soon!) they forget
When I've moved on to some other place,
There may be one or two,
When I've played and passed through,
Who'll remember my song or my face.
 
R

Robert Klemme

Complex numbers come to mind:

Why bother, it has been done already.

irb(main):006:0> x =3D Complex(0,-1)
=3D> (0-1i)
irb(main):007:0> x * x
=3D> (-1+0i)
irb(main):008:0> (x * x)+0
=3D> (-1+0i)
irb(main):009:0> (x * x).to_int
=3D> -1

And they do play nicely as ints - as long as it's possible:

irb(main):011:0> %w{foo bar baz}[x*x]
=3D> "baz"
irb(main):012:0> %w{foo bar baz}[x]
RangeError: can't convert 0-1i into Integer
from (irb):12:in `to_i'
from (irb):12:in `to_int'
from (irb):12:in `[]'
from (irb):12
from /opt/bin/irb19:12:in ` said:
3 - 2j [+|*] 45^(j * e * 44=B0).

Very different semantics for addition and multiplication of those than
for your normal space numbers, including conversion from Cartesian to
polar form. Bit of a textbook case for the benefits of inheritance and
function overloading. :p

It'd be better to have those be a sub-class of Float, though.

Actually it's Numeric which is correct because not every Complex _is a_ Flo=
at!

irb(main):010:0> Complex.ancestors
=3D> [Complex, Numeric, Comparable, Object, Kernel, BasicObject]

Cheers

robert

--=20
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/
 
P

Phillip Gawlowski

Actually it's Numeric which is correct because not every Complex _is a_ F=
loat!

Unless you convert from polar form* to Cartesian form:

Let's take "45e^(j * 44=B0)":

32.37029101523930127102246035055203587038080515238845970760... +
31.25962667065487789953828347402088034639160866937838052053... i

From <http://www.wolframalpha.com/input/?i=3D45*e^(44°i)>.

* Which you are much more likely to encounter than the Cartesian form,
considering complex numbers are most useful when dealing with wave
forms.

--=20
Phillip Gawlowski

Though the folk I have met,
(Ah, how soon!) they forget
When I've moved on to some other place,
There may be one or two,
When I've played and passed through,
Who'll remember my song or my face.
 
R

Robert Klemme

Float!

Unless you convert from polar form* to Cartesian form:

Let's take "45e^(j * 44=B0)":

32.37029101523930127102246035055203587038080515238845970760... +
31.25962667065487789953828347402088034639160866937838052053... i

From <http://www.wolframalpha.com/input/?i=3D45*e^(44°i)>.

* Which you are much more likely to encounter than the Cartesian form,
considering complex numbers are most useful when dealing with wave
forms.

My math is a bit rusty in that area, but I don't think your argument
holds: cartesian and polar are just two ways to represent a complex
number. But this does not change numeric properties of the class of
complex numbers. No matter what representation you use, (0+1i) is
neither a real number (what float conceptually models) nor a rational
number (what float technically implements). Instead, real and
rational numbers are both subsets of complex.

If you see inheritance as "is a" relationship then inheritance would
be Rational < Real < Complex but not the other way round. Otherwise
you cannot use a subclass instance everywhere you were using a
superclass instance.

Further links:

http://en.wikipedia.org/wiki/Rational_number
http://en.wikipedia.org/wiki/Real_number
http://en.wikipedia.org/wiki/Complex_number

Kind regards

robert

--=20
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/
 
P

Phillip Gawlowski

My math is a bit rusty in that area, but I don't think your argument
holds: cartesian and polar are just two ways to represent a complex
number. =A0But this does not change numeric properties of the class of
complex numbers. =A0No matter what representation you use, (0+1i) is
neither a real number (what float conceptually models) nor a rational
number (what float technically implements). =A0Instead, real and
rational numbers are both subsets of complex.

Given that there are infinitely more irrational than rational numbers,
it's much more common to represent a complex number with irrational
numbers than rational ones (i.e. floats instead of integers). Thus,
most (for want of a better word) real mathematical operations done
with complex numbers are done with irrational numbers. Cartesian and
polar forms make certain mathematical operations easier, but that's
more or less it (the truth is more complex, but I CBA to look into
trascendental numbers, Euler's number, &c.).

And yes, both rational and irrational numbers are subsets of complex,
obviously (with rational numbers being a subset of irrational numbers,
to simplify extremely). :)
If you see inheritance as "is a" relationship then inheritance would
be Rational < Real < Complex but not the other way round. =A0Otherwise
you cannot use a subclass instance everywhere you were using a
superclass instance.

Though, does the "is a" relationship hold up? I think it's more of a
"kind of" relationship, where subsequent classes are defined in ever
more detail (so, you'd inherit Floats from Integers, and Complex from
Float).

Of course, the clean world of maths doesn't map 1:1 to computational
systems, so I see the value in both approaches.

But, frankly, given the differences and additional properties of
complex numbers, I'd derive it from Numeric as well, simply to limit
the side effects the other numeric classes introduce (Floats and their
CPU-internal representation give me nightmares :p).

--=20
Phillip Gawlowski

Though the folk I have met,
(Ah, how soon!) they forget
When I've moved on to some other place,
There may be one or two,
When I've played and passed through,
Who'll remember my song or my face.
 
R

Robert Klemme

On Thu, Apr 7, 2011 at 3:43 PM, Robert Klemme


Though, does the "is a" relationship hold up? I think it's more of a
"kind of" relationship, where subsequent classes are defined in ever
more detail (so, you'd inherit Floats from Integers, and Complex from
Float).

Well, even with technical inheritance ("kind of") sub often add state
(i.e. member variables) but do only restrict valid values of
superclass state if at all. The cannot do otherwise because then
superclass methods may break. Silly example: superclass holds an
index which must be >=3D 0. All superclass methods use that index for
some kind of lookup. Assuming a sub class would suddenly set that
value to -13 the superclass contract would be violated. Now, if you
let Complex inherit from Real (trying to avoid "irrational" :)) you
would add another field for imaginary part. So far so good, but
method to_f would sometimes throw an exception in Complex which it
would never do in Real. So suddenly Complex breaks Real's contract.

Of course, all those considerations are far less important in a nicely
duck typed language like Ruby compared to a statically typed language.
Assuming you would do the same in Java you would have to declare the
exception (if you use checked exceptions) on Real class but state at
the same time that this class would never throw it. Even worse, all
code using Real would have to deal with this exception by either
catching or propagating it. Not nice.

That's why I prefer to look at inheritance as "is a" relationship:
after all OO is about better abstraction capabilities and to be able
to hide implementation details behind a clearly defined clean
interface. If you let yourself get dragged too much into technical
issues chances are that the design comes out awful. Only languages
which allow to inherit without publishing all features of the
inherited class (private inheritance e.g. in Eiffel) do not
necessarily suffer from these issues. But then, inheritance is just
an implementation detail in such cases.
Of course, the clean world of maths doesn't map 1:1 to computational
systems, so I see the value in both approaches.

That's true. I remember debates about the very question how to model
inheritance hierarchies for numeric types. Unfortunately I can't
produce a reference right now. Maybe someone else can.
But, frankly, given the differences and additional properties of
complex numbers, I'd derive it from Numeric as well, simply to limit
the side effects the other numeric classes introduce (Floats and their
CPU-internal representation give me nightmares :p).

:) Also, with Ruby's concept of coercion inheritance between numeric
types is probably less of an issue.

Kind regards

robert

--=20
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/
 
P

Phillip Gawlowski

Well, even with technical inheritance ("kind of") sub often add state
(i.e. member variables) but do only restrict valid values of
superclass state if at all. =A0The cannot do otherwise because then
superclass methods may break. =A0Silly example: superclass holds an
index which must be >=3D 0. =A0All superclass methods use that index for
some kind of lookup. =A0Assuming a sub class would suddenly set that
value to -13 the superclass contract would be violated. =A0Now, if you
let Complex inherit from Real (trying to avoid "irrational" :)) you
would add another field for imaginary part. =A0So far so good, but
method to_f would sometimes throw an exception in Complex which it
would never do in Real. =A0So suddenly Complex breaks Real's contract.

But that's a failure of implementation, isn't it?

If I were to implement my own class Complex, I'd have to deal with the
edge-cases that my sub-class has and can produce.

Thus, I either undefine #to_f, or redefine it so that it throws an
Exception. the value of inheritance is, after all, generalization, so
that I don't have to reimplement the wheel all the time, instead
making the wheel bigger or smaller, as the implementation requires.
That conversely also means that that more specialized sub-classes
derived from a generic-er super-class, *has* to implement an interface
that works, and works consistently.

To stay with Complex as an example:
#to_f would require an additional argument to work properly: Either
convert the real, or the imaginary part into a Float, and so would
anything derived from Complex, whatever that may be, if it has the
same properties.
That's why I prefer to look at inheritance as "is a" relationship:
after all OO is about better abstraction capabilities and to be able
to hide implementation details behind a clearly defined clean
interface. =A0If you let yourself get dragged too much into technical
issues chances are that the design comes out awful. =A0Only languages
which allow to inherit without publishing all features of the
inherited class (private inheritance e.g. in Eiffel) do not
necessarily suffer from these issues. =A0But then, inheritance is just
an implementation detail in such cases.

But isn't it always?

Regarding technical issues: Design is a bit of an art; knowing when to
stop abstracting is important. ;)

--=20
Phillip Gawlowski

Though the folk I have met,
(Ah, how soon!) they forget
When I've moved on to some other place,
There may be one or two,
When I've played and passed through,
Who'll remember my song or my face.
 
R

Robert Klemme

But that's a failure of implementation, isn't it?

That too, but the root cause lies in the area of the incompatibility
of the design with the properties of numerical classes.
If I were to implement my own class Complex, I'd have to deal with the
edge-cases that my sub-class has and can produce.

Thus, I either undefine #to_f, or redefine it so that it throws an
Exception. the value of inheritance is, after all, generalization, so
that I don't have to reimplement the wheel all the time, instead
making the wheel bigger or smaller, as the implementation requires.
That conversely also means that that more specialized sub-classes
derived from a generic-er super-class, *has* to implement an interface
that works, and works consistently.

To stay with Complex as an example:
#to_f would require an additional argument to work properly: Either
convert the real, or the imaginary part into a Float, and so would
anything derived from Complex, whatever that may be, if it has the
same properties.

But we were talking about the case of Complex inheriting Float. Your
arguments do not make sense with a Float class because there is no
imaginary part yet you would need them in order to be able to provide
a meaningful to_f in the subclass Complex.
But isn't it always?

If you treat it as such it is. However then you using a powerful
feature for abstraction and modeling.
Regarding technical issues: Design is a bit of an art; knowing when to
stop abstracting is important. ;)

In my experience far too many people in our profession have the other
problem: they dive into details too fast and do not think on an
abstract level. That's the reason why so much code I get to see has
issues. And since design flaws are generally much more costly to
repair than mere technical issues I'd rather say people should learn
to _start_ abstracting. :)

Thank you for the interesting discussion!

Cheers

robert


PS: I just read that there was another earthquake in Japan and there
is a tsunami warning. I hope the best for everybody in that area and
I hope these catastrophes end rather sooner than later.

--=20
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/
 
P

Phillip Gawlowski

But we were talking about the case of Complex inheriting Float. =A0Your
arguments do not make sense with a Float class because there is no
imaginary part yet you would need them in order to be able to provide
a meaningful to_f in the subclass Complex.

That's why Complex adds to #to_f, to enable the #to_f functionality
(or raise an error, when it just doesn't make sense to have a
function). It's method overloading.
Thank you for the interesting discussion!

My pleasure. :)

We can probably go back and forth over the benefits of the approaches,
but it's largely academical now, I think. ;)
PS: I just read that there was another earthquake in Japan and there
is a tsunami warning. =A0I hope the best for everybody in that area and
I hope these catastrophes end rather sooner than later.

Aye.

--=20
Phillip Gawlowski

Though the folk I have met,
(Ah, how soon!) they forget
When I've moved on to some other place,
There may be one or two,
When I've played and passed through,
Who'll remember my song or my face.
 
B

Brian Candler

Phillip Gawlowski wrote in post #991471:
Though, does the "is a" relationship hold up? I think it's more of a
"kind of" relationship, where subsequent classes are defined in ever
more detail (so, you'd inherit Floats from Integers, and Complex from
Float).

Isn't this the old "ellipse is_a circle, or vice versa" debate?

If you make Circle the top class, then Ellipse reimplements pretty much
everything (draw, area, etc); there's no useful code sharing. If you
make Ellipse the top class, then Circle is just a special constrained
case of Ellipse.

Translating to the current discussion, substitute Float for Circle and
Ellipse for Complex.

Ruby's answer is: neither is a subclass of the other. Both inherit from
Numeric. That is, Circle and Ellipse are both a Shape. Or in other
words, "who cares"?

Eventually you come to realise that a lot of what is taught in object
oriented classes and textbooks is tosh :)
 
V

Vincent Manis

Eventually you come to realise that a lot of what is taught in object
oriented classes and textbooks is tosh :)

And a lot of what is done by practitioners using object-oriented languages
is tosh as well, I know, I've seen it. (You haven't lived until you've had
to review a C++ class with 9-way multiple inheritance, without using rude
words!)

The Circle and Ellipse example is a good one. In fact, a Circle is no more
than an Ellipse with a constraint (eccentricity = 0, or, equivalently, the
two foci (`focuses') of the Ellipse are at the same point). So in almost
all cases, I wouldn't have two separate classes, but one, Ellipse.

In the case of Complex and Float, the operative design principle is the
Liskov Substitution Principle, which can be roughly stated in OO form as
`you can derive class Sub from class Super if and only if every instance
of Sub can be regarded as an instance of Super'.

Thus it's perfectly reasonable to derive JetPlane from Airplane, because
every JetPlane should be able to respond to all Airplane operations.
However, you can't derive Airplane from Wheel, or Wheel from Airplane,
even though there is some connection between wheels and planes. Like all
design principles, there are exceptional cases where the LSP doesn't apply,
but it seems to be the best heuristic for permissible subclassing.

In the Complex/Float case, the LSP tells us that we _could_ consider Float
a subclass of Complex (because every Float is, as has been pointed out, a
Complex with an imaginary part of zero), but that Complex can't reasonably
be considered a subclass of Float. A good designer would go further and say
`yes, the LSP allows me to derive Float from Complex, but that's a waste of
storage, because it means I must store imaginary parts that are always zero.'
Thus a better design (which Ruby follows) derives Complex from Numeric.

That's the OO theory, and it's not tosh :)

-- vincent manis
 
M

Mazi Ayışığı

Halk,

Ben Tamsayý için delegeler ve çok gibi davranmasýný isteyen birsýnýf varmümkün olduðu kadar gerçek bir Tamsayý (subclassed olmak için güçlü olmak hariç) gibi.O * çoðunlukla * çalýþýyor ...ancak, Ruby'nin çeþitli kesmek, hatalarýn faul düþüyorFixnum ve Hash sýnýflarda iç optimizasyonlar.

Özellikle, Hash uygulamalarý iþ (ve mola!) farklý olarakMRG, Rubinius ve JRuby.Sadece # hash ve # eql kullanmak belgelenir?,ama (bazen bu kablolu optimsations var) her zaman doðru deðil.

Hash belgeleri isteyip # eql demiyor?Sadece adý verilecektuþlarý, ya da sadece karma öðeleri hash soruþturma için kullanýlýyor.Olmasý gerektiðiolarak bir veya diðer, a.eql (b) her zaman b.eql anlamýna gelmiyor olabilir? beri? (a).

<: Bu kodu incelemek Lütfengist.github.com/906998 https: / /> üzerinde dene,çeþitli Ruby sürümleri ve ayrýca Fixnum maymun-yamalar iledeneyinkaldýrýldý.

Bu davranýþa çok öngörülemez olduðunu görürsünüz.

Clifford Heath.
 
R

Robert Klemme

Phillip Gawlowski wrote in post #991471:

Isn't this the old "ellipse is_a circle, or vice versa" debate?

If you make Circle the top class, then Ellipse reimplements pretty much
everything (draw, area, etc); there's no useful code sharing. If you
make Ellipse the top class, then Circle is just a special constrained
case of Ellipse.

Translating to the current discussion, substitute Float for Circle and
Ellipse for Complex.

Ruby's answer is: neither is a subclass of the other. Both inherit from
Numeric. That is, Circle and Ellipse are both a Shape. Or in other
words, "who cares"?

ACK, ACK and ACK.
Eventually you come to realise that a lot of what is taught in object
oriented classes and textbooks is tosh :)

I'd rather say they are incomplete. You need those simple examples to
explain what inheritance is for but often books stop right there.
Looking at inheritance on a very abstract level is worthwhile to start
musing about inheritance and how it can best be utilized. But you
then need to progress discussing technical aspects etc. like we do
here. But that is more difficult and complex and maybe some authors
are lazy, not aware of the complexity or do not delve into this for
other reasons. I found Betrand Meyer's "Object Oriented Software
Construction" very comprehensive as he covers a lot aspects of
inheritance. I would readily recommend it to anyone who wants to dive
a bit deeper into the matter.

Kind regards

robert
 
C

Clifford Heath

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?
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.
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.
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.
 
R

Robert Klemme

What's wrong with the case you use in that blog post?

You mean, make HexNum a subclass of Integer? Yes, actually that's
what I had attempted at the time but failed for technical reasons
(explained in the blog). As it turns out it's generally not necessary
to inherit Integer in Ruby to create a class which behaves like an
integer (most of the time).
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.

Where exactly do you see the inconsistency? I can see that a few
things in that area do not match common expectations. But I don't
think it's really inconsistent.

Your problem is not so much with numeric classes IMHO but rather with
implementations of class Hash in different versions of Ruby. Namely
do they have issues treating instances from different class as
equivalent.
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?

Fixnum and Bignum do not share common values so you never have
instances of different classes representing the same numeric integer
value:

irb(main):003:0> (1<<100).class
=3D> Bignum
irb(main):004:0> (1<<100)>>99
=3D> 2
irb(main):005:0> ((1<<100)>>99).class
=3D> Fixnum

So that situation is a bit different.
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.)

Yes, you're right. I probably mixed in a discussion about equals() in
Java needing to test for the same class (and not instanceof) to
achieve real equivalence. At least we had a nice discussion about OO
and inheritance because of that. :)
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.

Apparently there are optimizations done under the hood (similarly to
duping an unfrozen String as key) which is probably OK from a
pragmatic point of view (what you attempt seems rather seldom done).
The Ruby interpreters should behave the way the Hash documentation says t= hey
do.

Well, they do - most of the time. :)
The Ruby documentation should explicitly state that eql? must be defined
symmetrically, or should require that the Hash implementation uses it onl= y
in a known direction or both.

Right, there is certainly room for improvement.
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 n= ot
stated.

Right again. Maybe the requirement can be inferred from other
properties but it would certainly make sense to stress it.
 
X

Xavier Noria

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).

Just in case, that happened also to me. Problem was all confirmation mails w=
ere in the spam folder.
 
C

Clifford Heath

You mean, make HexNum a subclass of Integer? Yes, actually that's
what I had attempted at the time but failed for technical reasons

No, I don't mean making HexNum a subclass of Integer, but making it an
"integer like class" which can be subclassed.
(explained in the blog). As it turns out it's generally not necessary
to inherit Integer in Ruby to create a class which behaves like an
integer (most of the time).

Right. I'd like to see that work *more* of the time :). Or at least,
that each Ruby interpreter should fail in the same way.
Where exactly do you see the inconsistency? I can see that a few
things in that area do not match common expectations. But I don't
think it's really inconsistent.

By inconsistent, I mean that Ruby doesn't make it possible to make
subclasses of Integer that play nicely with other Integers. Fixnum
and Bignum are mutually compatible and automatically and invisibly
convert back and forth, but it's not possible for an user's class to
do the same. That's inconsistent. A few more calls to coerce and some
more circumspect interpreter optimisations and it would all be pretty
ok.

Note that I expect there will still be a need for Java-style boxed
and unboxed integer values. C# makes the boxing even more transparent
than Java, but Ruby doesn't even try.
Your problem is not so much with numeric classes IMHO but rather with
implementations of class Hash in different versions of Ruby. Namely
do they have issues treating instances from different class as
equivalent.

Yes. It's documented to use #hash and #eql?, so that's what it should do.
If it also has invisible optimisations, fine. So long as they're invisible.
Fixnum and Bignum do not share common values so you never have
instances of different classes representing the same numeric integer
value:

Yes. But I never need to know where the cut-over is, and it can be different
with different Ruby build targets. It's almost completely transparent.
Apparently there are optimizations done under the hood (similarly to
duping an unfrozen String as key)

Except that the case of String is documented, and works the same in all
interpreters.
which is probably OK from a
pragmatic point of view (what you attempt seems rather seldom done).

Mainly because it doesn't work :)

Clifford Heath.
 
C

Clifford Heath

Just in case, that happened also to me. Problem was all confirmation mails were in the spam folder.

Thanks, that was the problem (/me hides face).
A few more attempts and I'm subscribed.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,764
Messages
2,569,565
Members
45,041
Latest member
RomeoFarnh

Latest Threads

Top