From: dblack@... Date: 2007-09-03T20:32:35+09:00 Subject: Re: assigning to hash keys when there is a default value? Hi -- On Mon, 3 Sep 2007, Robert Klemme wrote: > 2007/9/3, dblack@wobblini.net : >> Hi -- >> >> On Mon, 3 Sep 2007, 7stud -- wrote: >> >>> Can someone explain why there is a difference in the second line of >>> output for the two hashes: >>> >>> h = Hash.new(5) >>> >>> puts h[2] >>> >>> h[2] ||= 10 >>> p h >>> >>> #---------- >>> puts >>> #---------- >>> >>> h = Hash.new >>> >>> puts h[2] >>> >>> h[2] ||= 10 >>> p h >>> >>> ---output:-- >>> 5 >>> {} >>> >>> nil >>> {2=>10} >> >> x ||= y is, I think, always supposed to be exactly equivalent to >> x = x || y, so that line in your first hash should be equivalent to: >> >> h[2] = 5 || 10 >> >> which should assign 5 to h[2]. It looks to me like you've found a bug. >> I can't think of any reason (and I really hope there isn't one, >> because having an exception to that ||= rule would be very messy) why >> using a default hash value would make any difference here. It's still >> 5 || 10 on the rhs, and it's still just an assignment. > > I can't point my finger on it but I believe x||=y is equivalent to > "x=y unless x" instead of "x=x||y". It seems to be more reasonable to > skip the assignment altogether if the value is true equivalent > already. That would also explain behavior much better. :-) I still don't like it. I'm not sure about that "unless" thing.... I always thought x ||= y was strictly syntactic sugar for x = x || y. I'm now thinking about how much fun it will be to explain to people learning Ruby that that's true, unless the lhs is a call to Hash#[] on a hash with a default value with a boolean value of true, in which case...... What fun :-) > Note also: > > $ ruby -e 'h=Hash.new 2;set_trace_func lambda {|*a| p a}; h[4]||=10' > ["line", "-e", 1, nil, #, false] > ["c-call", "-e", 1, :[], #, Hash] > ["c-call", "-e", 1, :default, #, Hash] > ["c-return", "-e", 1, :default, #, Hash] > ["c-return", "-e", 1, :[], #, Hash] > > $ ruby -e 'h=Hash.new 2;set_trace_func lambda {|*a| p a}; h[4]=h[4]||10' > ["line", "-e", 1, nil, #, false] > ["c-call", "-e", 1, :[], #, Hash] > ["c-call", "-e", 1, :default, #, Hash] > ["c-return", "-e", 1, :default, #, Hash] > ["c-return", "-e", 1, :[], #, Hash] > ["c-call", "-e", 1, :[]=, #, Hash] > ["c-return", "-e", 1, :[]=, #, Hash] > > There is no assignment in the first piece. I wonder whether there's some special optimization for hashes. I can't find any other example where the setter method isn't called (except attr_accessor-created setters, which I haven't puzzled through yet). I won't paste the whole output but run this: class C def x; @x; end def x=(x); @x = x; end end c = C.new set_trace_func lambda {|*a| p a } c.x ||= 1 puts c.x = nil puts c.x = c.x || 1 puts a = [] puts a[1] ||= 1 puts a[2] = a[2] || 1 As far as I can tell, the sugar version and the inlined version work exactly the same, except for hashes. My vote is still for bug :-) David -- * Books: RAILS ROUTING (new! http://www.awprofessional.com/title/0321509242) RUBY FOR RAILS (http://www.manning.com/black) * Ruby/Rails training & consulting: Ruby Power and Light, LLC (http://www.rubypal.com)