On Fri, Apr 8, 2011 at 9:30 AM, Clifford H. [email protected]
wrote:
On 04/07/11 19:19, Robert K. wrote:
On Thu, Apr 7, 2011 at 6:05 AM, Clifford H.[email protected] wrote:
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?
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
=> Bignum
irb(main):004:0> (1<<100)>>99
=> 2
irb(main):005:0> ((1<<100)>>99).class
=> Fixnum
So that situation is a bit different.
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.)
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 they
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 only
in a known direction or both.
Right, there is certainly room for improvement.
stated.
Right again. Maybe the requirement can be inferred from other
properties but it would certainly make sense to stress it.
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.
But since you already embarked in monkey patching core classes you can
easily extend that a bit to Hash#[] and Hash#fetch. That should work
on all platforms. Another possible remedy would be to wrap Hash
instances in something else which adds logic to [] and fetch() to
convert types if necessary.
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.
As said, they do it most of the time. You introduced a corner case
here by fiddling with a core class which is known to lead into deep
water. Treating instances from different classes equivalent does work
for other classes:
12:07:22 Temp$ allruby ha.rb
CYGWIN_NT-5.1 padrklemme2 1.7.9(0.237/5/3) 2011-03-29 10:10 i686 Cygwin
ruby 1.8.7 (2008-08-11 patchlevel 72) [i386-cygwin]
[[#<B:0x7ff9faa4 @v=1>, 3, 1],
[#<A:0x7ff9fa90 @v=2>, 5, 2],
[1, 3, nil],
[2, 5, nil]]
ruby 1.9.2p180 (2011-02-18 revision 30909) [i386-cygwin]
[[#<B:0x1003a290 @v=1>, 129497438, 1],
[#<A:0x1003a27c @v=2>, 602294680, 2],
[1, 129497438, nil],
[2, 602294680, nil]]
jruby 1.6.0 (ruby 1.8.7 patchlevel 330) (2011-03-15 f3b6154) (Java
HotSpot™ Client VM 1.6.0_24) [Windows XP-x86-java]
[[#<B:0xfa8 @v=1>, 1, 1], [#<A:0xfb0 @v=2>, 2, 2], [1, 1, nil], [2, 2,
nil]]
jruby 1.6.0 (ruby 1.9.2 patchlevel 136) (2011-03-15 f3b6154) (Java
HotSpot™ Client VM 1.6.0_24) [Windows XP-x86-java]
[[#<B:0x000000 @v=1>, 1, 1],
[#<A:0x000000 @v=2>, 2, 2],
[1, 1, nil],
[2, 2, nil]]
12:07:36 Temp$ cat -n ha.rb
1
2 require ‘pp’
3
4 A, B = 2.times.map do
5 Class.new do
6 def initialize(x)
7 @v = x.to_i
8 end
9
10 def to_i
11 @v
12 end
13
14 def hash
15 @v.hash
16 end
17
18 def eql? o
19 case o
20 when A, B
21 @v == o.to_i
22 end
23 end
24
25 alias == eql?
26 end
27 end
28
29 h = {A.new(1) => 1, B.new(2) => 2}
30
31 keys = [B.new(1), A.new(2), 1, 2]
32
33 pp keys.map {|k| [k, k.hash, h[k]]}
34
35
12:07:40 Temp$
Please try to read more carefully.
Will do.
Cheers
robert