From: Rick DeNatale Date: 2007-09-05T03:58:22+09:00 Subject: Re: assigning to hash keys when there is a default value? On 9/4/07, dblack@wobblini.net wrote: > Hi -- > > On Wed, 5 Sep 2007, Rick DeNatale wrote: > > > On 9/3/07, Robert Klemme wrote: > >> 2007/9/3, dblack@wobblini.net : > > > >>> x ||= y is, I think, always supposed to be exactly equivalent to > >>> x = x || y, ... > > > >> 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. :-) > > > > Robert, > > > > Although I can't find the documentation quickly, although I'm 95% > > certain that it should be in the pickaxe somewhere, I'm pretty sure > > that you are correct. > > > > I've just looked at parse.y and eval.c for ruby1.8.6 and it would appear that: > > > > h[2] ||= 10 > > > > gets compiled to a NODE_OP_ASGN_OR node with h[2] as the lhs and 10 as > > the rhs. Here's the code from eval.c which evaluates such a node: > > > > case NODE_OP_ASGN_OR: > > if ((node->nd_aid && !is_defined(self, node->nd_head, 0)) || > > !RTEST(result = rb_eval(self, node->nd_head))) { > > node = node->nd_value; > > goto again; > > } > > break; > > > > So what happens is that the lhs is only evaluated if the the lhs > > (node->nd_head) is not defined || it evaluates to an untrue value. > > > > In the case of h[5] the default value for the hash means that it will > > evaluate to 5, and the assignment is not done. > > > > I for one, am glad that it works this way. The ruby idiom > > > > x ||= y > > > > is heavily used for lazy initialization/caching. While most often, > > it's the rhs which is expensive to compute and therefore the thing we > > want to short-circuit, since x= can in general be a method, and might > > just be expensive, then optimizing the case where it boils down to x = > > x as a nop, makes sense. > > I think The method always gets called, though: > > class C > attr_reader :x > def x=(n) > puts "C#x=" > true > end > end > > c = C.new > c.x ||= 3 # C#x= > > *Unless*, of course, the object is a Hash which has either (a) a key > corresponding to the indicated value, or (b) a default value with > boolean truth value. No, in the case you posited, the assignment happened and C#x= got called because c.x returned nil. irb(main):001:0> class D irb(main):002:1> def x irb(main):003:2> @x || 5 irb(main):004:2> end irb(main):005:1> def x=(v) irb(main):006:2> puts "x=called" irb(main):007:2> @x = v irb(main):008:2> end irb(main):009:1> end => nil irb(main):010:0> d = D.new => # irb(main):011:0> d.x ||= 10 => 5 irb(main):012:0> d.x => 5 irb(main):013:0> d.x=10 x=called => 10 irb(main):014:0> d.x => 10 irb(main):015:0> > Sigh. I really wish it were otherwise. What an annoying exception to > the rule. Except that the 'rule' wasn't as you thought. The rule is that x ||= y is the same as x = y unless x > Also, in the famous: > > h = Hash.new(1) > h[5] ||= 10 > > case, it definitely isn't doing the "x = x" equivalent, since that > would set the 5 key to 1. And that's as expected because h[5] returns the default value and doesn't affect the state of the hash a whit, it doesn't create a 5 key. If you want the default to affect the hash you need something like hsh = Hash.new {|h,k| h[k] = 10} $ fri Hash.new -------------------------------------------------------------- Hash::new Hash.new => hash Hash.new(obj) => aHash Hash.new {|hash, key| block } => aHash ------------------------------------------------------------------------ Returns a new, empty hash. If this hash is subsequently accessed by a key that doesn't correspond to a hash entry, the value returned depends on the style of new used to create the hash. In the first form, the access returns nil. If obj is specified, this single object will be used for all default values. If a block is specified, it will be called with the hash object and the key, and should return the default value. It is the block's responsibility to store the value in the hash if required. h = Hash.new("Go Fish") h["a"] = 100 h["b"] = 200 h["a"] #=> 100 h["c"] #=> "Go Fish" # The following alters the single default object h["c"].upcase! #=> "GO FISH" h["d"] #=> "GO FISH" h.keys #=> ["a", "b"] Note the value of h.keys at the end of the RI example. > > I don't know.... However many times I look at it, I just can't see > this: > > h[5] ||= 10 > > as *not* meaning that I expect h to end up having a 5 key, one way or > another. Maybe just one more try?! -- Rick DeNatale My blog on Ruby http://talklikeaduck.denhaven2.com/