Bug #4323

Proc#hash is Ill-Behaved

Added by Run Paint Run Run about 3 years ago. Updated almost 2 years ago.

[ruby-core:34854]
Status:Closed
Priority:Low
Assignee:Yukihiro Matsumoto
Category:core
Target version:-
ruby -v:ruby 1.9.3dev (2011-01-19 trunk 30597) [x86_64-linux] Backport:

Description

=begin
Proc#hash is not predictable for receivers that are #eql?

 irb> ->{}.hash
 => -1250381286238705236
 irb> ->{}.hash
 => 2684657672161532106
 irb> ->{true}.hash
 => -2939885723276364833
 irb> ->{true}.hash
 => -3124033653404588619
 irb> proc{}.hash
 => -47014914982973166
 irb> proc{}.hash
 => -744699484074399895

=end

proc-hash.patch Magnifier (650 Bytes) Run Paint Run Run, 01/26/2011 06:35 PM


Related issues

Related to ruby-trunk - Bug #4559: Proc#== does not match the documented behaviour Closed 04/07/2011

History

#1 Updated by Charles Nutter about 3 years ago

=begin
I'm not so sure I'd expect Proc#hash to be equal in these cases. Of course, I don't feel like Procs that simply have the same code and closure should be expected to be eql? either.

Each proc has its own scope and its own reference to closed-over containing scope. They can be used independently and have independent state of their own. Should they be eql? and have identical hash values?

What constitutes equivalent procs? Just the code it contains? That's obviously not enough, since they'll have access to different closed-over state. The code it contains and the lexical scope in which it is instantiated? That's not consistently supported either, for whatever reason:

~/projects/jruby ➔ ruby1.9 -e "x = ->{def foo; end}; y = ->{def foo; end}; p x.eql? y"
false

~/projects/jruby ➔ ruby1.9 -e "x = ->{:foo}; y = ->{:foo}; p x.eql? y"
true

~/projects/jruby ➔ ruby1.9 -e "x = ->{'foo'}; y = ->{'foo'}; p x.eql? y"
false

~/projects/jruby ➔ ruby1.9 -e "x = ->{if true; false; end}; y = ->{if true; false; end}; p x.eql? y"
true

~/projects/jruby ➔ ruby1.9 -e "x = ->{->{}}; y = ->{->{}}; p x.eql? y"
false

~/projects/jruby ➔ ruby1.9 -e "x = ->{5}; y = ->{5}; p x.eql? y"
true

~/projects/jruby ➔ ruby1.9 -e "x = ->{5.0}; y = ->{5.0}; p x.eql? y"
false

~/projects/jruby ➔ ruby1.9 -e "x = ->{[]}; y = ->{[]}; p x.eql? y"
true

~/projects/jruby ➔ ruby1.9 -e "x = ->{[5.0]}; y = ->{[5.0]}; p x.eql? y"
false

~/projects/jruby ➔ ruby1.9 -e "x = proc {}; y = lambda {}; p x.eql? y"
true

I only searched for a few minutes and came up with all these inconsistencies in 1.9's Proc#eql?. Obviously the containing scope and the code within the proc are not enough to determine eql?, and simple cases like ->{'foo'}, which should be easy to call "equivalent", make me believe it's not a good idea to trust Proc#eql? in any case anyway. And if you can't trust eql? I'm not sure how useful it is to trust hash.

Maybe someone more familiar with MRI can explain what criteria should be used to determine equivalence of two procs? I can't figure it out from the above examples.

There may also be evidence here that this is a very implementation-specific detail. In JRuby, when a given Proc has been JIT-compiled, should it still be eql? the same proc that has not been compiled? In the case of precompiling a Proc, so that the original AST no longer exists at runtime, what would be use to determine the proc is equivalent to some other proc?
=end

#2 Updated by Charles Nutter about 3 years ago

=begin
I believe your example case, run in IRB, is also flawed. Witness:

~/projects/jruby ➔ irb1.9

->{}
=> #
->{}
=> #
->{}
=> #

Oh ho you say, but that doesn't mean they should not be equivalent. Au contraire:

a = []
=> []
a << ->{}
=> [#]
a << ->{}
=> [#, #]
a[0].eql?(a[1])
=> false

IRB is a bad place to demonstrate such things because the bindings are changing around all the time. Your examples do show different hash values (and your expected eql? results) within a script, however.
=end

#3 Updated by Run Paint Run Run about 3 years ago

=begin

I'm not so sure I'd expect Proc#hash to be equal in these cases. Of course, I don't feel like
Procs that simply have the same code and closure should be expected to be eql? either.

Perhaps, but the Rdoc is clear that the current behaviour of #eql? is intended, and that of #hash is wrong. If you want the spec changed, thus breaking backward compatibility, that's another discussion.

I've attached my best effort at a fix. It doesn't work for Method#to_proc cases because #eql? doesn't, either. With the patch applied:

 run@desktop:~/mir/ruby$  →  ./ruby --disable-gems -e 'r=/#{1+2}/; p ->{:FOO; r}.hash; p ->{:FOO; r}.hash'
 661952060
 661952060
 run@desktop:~/mir/ruby$  →  ./ruby --disable-gems -e 'r=/#{1+2}/; p ->{:FOO; r}.hash; p ->{:FOO; r; 22}.hash'
 -522099608
 77097371

=end

#4 Updated by Yui NARUSE almost 3 years ago

  • Status changed from Open to Assigned
  • Assignee set to Yukihiro Matsumoto

#5 Updated by Koichi Sasada almost 2 years ago

  • Status changed from Assigned to Closed

Also available in: Atom PDF