Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

assigning to hash keys when there is a default value?

1 view
Skip to first unread message

7stud --

unread,
Sep 2, 2007, 9:54:17 PM9/2/07
to
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}
--
Posted via http://www.ruby-forum.com/.

dbl...@wobblini.net

unread,
Sep 2, 2007, 10:19:09 PM9/2/07
to
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.


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)

7stud --

unread,
Sep 2, 2007, 10:29:00 PM9/2/07
to
unknown wrote:
> 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 don't think it's a bug; the two examples are set forth on p. 276 of
"The Ruby Way (2nd ed)". However, no explanation is given for why the
two examples work differently.

dbl...@wobblini.net

unread,
Sep 3, 2007, 6:24:01 AM9/3/07
to
Hi --

On Mon, 3 Sep 2007, 7stud -- wrote:

It seems very bug-like to me. I don't know what Hal's take on it is.
Paging Hal....

Robert Klemme

unread,
Sep 3, 2007, 7:08:43 AM9/3/07
to
2007/9/3, dbl...@wobblini.net <dbl...@wobblini.net>:

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

Note also:

$ ruby -e 'h=Hash.new 2;set_trace_func lambda {|*a| p a}; h[4]||=10'
["line", "-e", 1, nil, #<Binding:0x1002ff48>, false]
["c-call", "-e", 1, :[], #<Binding:0x1002ff0c>, Hash]
["c-call", "-e", 1, :default, #<Binding:0x1002fdf4>, Hash]
["c-return", "-e", 1, :default, #<Binding:0x1002fcf0>, Hash]
["c-return", "-e", 1, :[], #<Binding:0x1002fc00>, Hash]

$ ruby -e 'h=Hash.new 2;set_trace_func lambda {|*a| p a}; h[4]=h[4]||10'
["line", "-e", 1, nil, #<Binding:0x1002fee4>, false]
["c-call", "-e", 1, :[], #<Binding:0x1002fea8>, Hash]
["c-call", "-e", 1, :default, #<Binding:0x1002fd90>, Hash]
["c-return", "-e", 1, :default, #<Binding:0x1002fc8c>, Hash]
["c-return", "-e", 1, :[], #<Binding:0x1002fb9c>, Hash]
["c-call", "-e", 1, :[]=, #<Binding:0x1002faac>, Hash]
["c-return", "-e", 1, :[]=, #<Binding:0x1002f9bc>, Hash]

There is no assignment in the first piece.

Kind regards

robert

7stud --

unread,
Sep 3, 2007, 7:21:37 AM9/3/07
to
Peña, Botp wrote:
> they should be different (note, that has nothing to do w the bug dBlack
> is pointing at) and the output should be,
>
> 5
> {2=>5} #<---dblack is complaining here because the ruby output is {}
>
> nil
> {2=>10}
>

Yes. That is what I am complaining about too! Your are right: the
second line for each section of the output should be different--I
mispoke. What I want to know is why the the output is {} and not
{2=>5}.

The author of "The Ruby Way (2nd ed)" presents the two examples as well
as the different output for each example, yet he doesn't explain the
reason for the different output. He suggests that you can use ||= to
selectively assign values only to keys that don't exist. The point I
think he was trying to make with the two examples is that when you have
a default value for non-existent keys, all keys "exist", so ||= will not
create new keys with the default value.

7stud --

unread,
Sep 3, 2007, 7:27:18 AM9/3/07
to
Peña, Botp wrote:
> pls refer to the hash#new document.
>

I also have "Programming Ruby (2nd ed)", and I read the documentation
therein for Hash.new many times before posting--which is the same
documentation that ri produces.

dbl...@wobblini.net

unread,
Sep 3, 2007, 7:32:35 AM9/3/07
to
Hi --

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, #<Binding:0x1002ff48>, false]
> ["c-call", "-e", 1, :[], #<Binding:0x1002ff0c>, Hash]
> ["c-call", "-e", 1, :default, #<Binding:0x1002fdf4>, Hash]
> ["c-return", "-e", 1, :default, #<Binding:0x1002fcf0>, Hash]
> ["c-return", "-e", 1, :[], #<Binding:0x1002fc00>, Hash]
>
> $ ruby -e 'h=Hash.new 2;set_trace_func lambda {|*a| p a}; h[4]=h[4]||10'
> ["line", "-e", 1, nil, #<Binding:0x1002fee4>, false]
> ["c-call", "-e", 1, :[], #<Binding:0x1002fea8>, Hash]
> ["c-call", "-e", 1, :default, #<Binding:0x1002fd90>, Hash]
> ["c-return", "-e", 1, :default, #<Binding:0x1002fc8c>, Hash]
> ["c-return", "-e", 1, :[], #<Binding:0x1002fb9c>, Hash]
> ["c-call", "-e", 1, :[]=, #<Binding:0x1002faac>, Hash]
> ["c-return", "-e", 1, :[]=, #<Binding:0x1002f9bc>, 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 :-)

dbl...@wobblini.net

unread,
Sep 3, 2007, 7:37:13 AM9/3/07
to
This message is in MIME format. The first part should be readable text,
while the remaining parts are likely unreadable without MIME-aware tools.

Russell Norris

unread,
Sep 3, 2007, 7:38:27 AM9/3/07
to
I don't think this is a bug, kittens. since h[2] returns a value [even
though it's not set], it causes h[2] to evaluate so the assignment
never happens. x ||= y just means give me x or set x to y if there's
no value for x. h[2] _does_ have a value if only a default one.

just my two cents.

unless i missed the point here, in which case i apologize for my hasty
conclusion. :))

RSL

7stud --

unread,
Sep 3, 2007, 7:47:03 AM9/3/07
to
unknown wrote:
> The only possible explanation I can come up with, which I don't like,
> is that:
>
> h[2] ||= 10
>
> is being treated like:
>
> 5 = 5 || 10
>
> which is just another can of worms.
>


I also considered that possibility. Does the trace output support that?
I don't really know how to read the trace output, but it doesn't look
like there are two calls to Hash#[] for each ||=.

Sebastian Hungerecker

unread,
Sep 3, 2007, 7:58:18 AM9/3/07
to
Russell Norris wrote:
> x ||= y just means give me x or set x to y if there's no value for x.

Maybe it does, but if we follow the logic that x op= y is a shortcut for
x = x op y, what it *should* mean is: set x to y if there's no value for
x, otherwise set it to itself.


--
NP: Graveworm - I - The Machine
Jabber: sep...@jabber.org
ICQ: 205544826

dbl...@wobblini.net

unread,
Sep 3, 2007, 8:29:55 AM9/3/07
to
Hi --

On Mon, 3 Sep 2007, Russell Norris wrote:

> I don't think this is a bug, kittens. since h[2] returns a value [even
> though it's not set], it causes h[2] to evaluate so the assignment
> never happens. x ||= y just means give me x or set x to y if there's
> no value for x. h[2] _does_ have a value if only a default one.
>
> just my two cents.
>
> unless i missed the point here, in which case i apologize for my hasty
> conclusion. :))

The thing is, this:

x ||= y

always means (at least, so I've always been told):

x = x || y

which in the case of the hash with a default value of 5, would mean:

h[2] = 5 || 10

In other words: h.[]=(2, 5||10)

At that point, the default value is out of the picture. The default
value has no implications for the #[]= (writer) method; it's only what
you get when you use the #[] (reader) method.

So what's at stake here is the matter of x ||= y behaving predictably.
I have to say, it's a case where I'm not even really concerned with
the implementation (e.g., whether it uses [] on its way to []=) but
just the semantics. I don't like the idea that the syntactic sugar is
actually not a reliable drop-in replacement for the thing it's
sugaring.

Morton Goldberg

unread,
Sep 3, 2007, 8:34:46 AM9/3/07
to
On Sep 3, 2007, at 7:37 AM, dbl...@wobblini.net wrote:

> The only possible explanation I can come up with, which I don't like,
> is that:
>
> h[2] ||= 10
>
> is being treated like:
>
> 5 = 5 || 10
>
> which is just another can of worms.

I don't want to believe that because it raises the question of why

h[2] = 10

isn't treated as

5 = 10

I mean, I simply can't believe the Ruby interpreter would treat h[2]
as a simple rvalue in h[2] ||= 10.

Robert Klemme explanation is more believable although I find it
somewhat upsetting after years of believing x =<operator> y is just
syntactic sugar for x = x <operator> y. The optimization he describes
is a plausible one, and I can't think of any other case where it
would backfire.

Regards, Morton

dbl...@wobblini.net

unread,
Sep 3, 2007, 8:37:11 AM9/3/07
to
Hi --

On Mon, 3 Sep 2007, Morton Goldberg wrote:

> On Sep 3, 2007, at 7:37 AM, dbl...@wobblini.net wrote:
>
>> The only possible explanation I can come up with, which I don't like,
>> is that:
>>
>> h[2] ||= 10
>>
>> is being treated like:
>>
>> 5 = 5 || 10
>>
>> which is just another can of worms.
>
> I don't want to believe that because it raises the question of why
>
> h[2] = 10
>
> isn't treated as
>
> 5 = 10

> I mean, I simply can't believe the Ruby interpreter would treat h[2] as a
> simple rvalue in h[2] ||= 10.

I agree -- it definitely wouldn't. It was a kind of reductio ad
absurdum.

Russell Norris

unread,
Sep 3, 2007, 9:32:21 AM9/3/07
to
I learned that x ||= y means set x to y unless x, so I don't see the
bug. and i don't see this as being at all unpredictable. either way
you expect it to work, it either fails 100% or succeeds 100%. :) the
only problem is when you expect it to do something it doesn't do,
which is tautologically what every problem is i guess.

RSL

dbl...@wobblini.net

unread,
Sep 3, 2007, 9:37:56 AM9/3/07
to
Hi --

On Mon, 3 Sep 2007, Russell Norris wrote:

> I learned that x ||= y means set x to y unless x, so I don't see the
> bug. and i don't see this as being at all unpredictable. either way
> you expect it to work, it either fails 100% or succeeds 100%. :) the
> only problem is when you expect it to do something it doesn't do,
> which is tautologically what every problem is i guess.

I guess one can just accomodate oneself to whatever happens, in which
case there are no bugs... but somehow it never seems to play out that
way :-) Anyway, we're going around in circles. I think I might hop
over to ruby-core and see what people think.

Robert Klemme

unread,
Sep 3, 2007, 9:55:23 AM9/3/07
to
2007/9/3, dbl...@wobblini.net <dbl...@wobblini.net>:

> Hi --
>
> On Mon, 3 Sep 2007, Russell Norris wrote:
>
> > I learned that x ||= y means set x to y unless x, so I don't see the
> > bug. and i don't see this as being at all unpredictable. either way
> > you expect it to work, it either fails 100% or succeeds 100%. :) the
> > only problem is when you expect it to do something it doesn't do,
> > which is tautologically what every problem is i guess.
>
> I guess one can just accomodate oneself to whatever happens, in which
> case there are no bugs... but somehow it never seems to play out that
> way :-) Anyway, we're going around in circles. I think I might hop
> over to ruby-core and see what people think.

Will be interesting to hear what they say.

In the meantime: I believe we're discussing something that's not an
issue most of the time, because even if there is no assignment with
the default value, querying will still return the same result. The
only possible issue here is that Hash#keys returns something different
depending on the implementation.

Why do I think there is no problem most of the time? Let's look at
some typical idioms which work as we like them to work:

counters = Hash.new 0
..
counters[item] += 1

counters.each do |key,value|
printf "%5d %s\n", value, key
end


lists = Hash.new {|h,k| h[k] = []}
..
lists[key] << item

lists.each do |key,list|
print key, " ", list.inspect, "\n"
end

I wonder where we would actually use ||= with a Hash. Is this a
realistic example?

found = Hash.new
..
found[item] ||= true

I don't think so, because in that case I'd rather use

found[item] = true

Is ||= really used with Hash at all? I can't think of a case ATM but
that might just be my limited fantasy. :-)

Kind regards

robert

Eric Promislow

unread,
Sep 3, 2007, 6:24:28 PM9/3/07
to
I always wondered why JavaScript is missing the '||='
operator (I rarely need a "&&=" one). Maybe Brendan
was prescient enough to realize an r-value interpretation
would lead to ambiguity. Maybe, but I still say
it's a bug. +1 for DavidB.

- Eric

Peña, Botp

unread,
Sep 3, 2007, 9:13:03 PM9/3/07
to
From: sco...@gmail.com [mailto:sco...@gmail.com] On Behalf Of Russell Norris:
# I learned that x ||= y means set x to y unless x, so I don't see the

there's the bug: you've changed the meaning of x ||= y, or of foo<op>=bar for that matter.

# bug. and i don't see this as being at all unpredictable.

you just said you've _learned that x||=y means set x to y unless x. Surely it was _unpredictable at some point. Surely x<op>=y is a no-brainer for a many of us :)

and now we all have to update our test cases, cause maybe x += 1 may not always be x = x + 1

speaking of least surprise :(

kind regards -botp



Russell Norris

unread,
Sep 4, 2007, 8:24:20 AM9/4/07
to
Ahhh now I see what you kittens are seeing. I _still_ don't think it's
a bug though. <op>|| might be a no-brainer but ||= isn't completely
analogous to methods like += since || itself isn't a method though it
is an operator. || always returns either/or. And in this case, I
really think the operator is doing the expected behavior even if the
<op>= might be a misleading analogy.

Also, I didn't mean that I learned from experience but that it was the
way I was taught this. In case that makes a difference. I dunno.

Finally, I musta missed something else 'cause x += 1 [unless x isn't
numeric to begin with] _always_ increments and you said that it might
not always do so. Apologies if I've missed some joke or something. :)

RSL

Robert Klemme

unread,
Sep 4, 2007, 8:32:58 AM9/4/07
to
2007/9/4, Peña, Botp <bo...@delmonte-phil.com>:

No, "x+=1" will always be "x=x+1".

robert

Yossef Mendelssohn

unread,
Sep 4, 2007, 9:29:35 AM9/4/07
to
On Sep 4, 7:32 am, "Robert Klemme" <shortcut...@googlemail.com> wrote:
> 2007/9/4, Peña, Botp <b...@delmonte-phil.com>:

If '"x+=1" will always be "x=x+1"', what's the problem in having x ||=
1 always be x = x || 1?

--
-yossef


Russell Norris

unread,
Sep 4, 2007, 9:35:11 AM9/4/07
to
The problem with that is that || doesn't work that way, imo.

x || whatever

only does whatever if x isn't true. Excuse me if I'm sounding like a
broken record but the more we talk about this the more I'm convinced
that this "bug" only exists if you expect || to act like + does just
because it's an "operator" instead of allowing for the fact that ||
isn't a method like + and friends.

|| is special. It takes another bus to ||= than + does, heh. ;)

Sorry for the silliness there.

RSL

Robert Klemme

unread,
Sep 4, 2007, 9:37:26 AM9/4/07
to
2007/9/4, Yossef Mendelssohn <yme...@pobox.com>:

That an assignment of x=x is useless (basically a nop in the standard
case of x being a variable). Please see also one of my earlier
postings.

Kind regards

robert

Rick DeNatale

unread,
Sep 4, 2007, 1:05:33 PM9/4/07
to
On 9/3/07, Robert Klemme <short...@googlemail.com> wrote:
> 2007/9/3, dbl...@wobblini.net <dbl...@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.
--
Rick DeNatale

My blog on Ruby
http://talklikeaduck.denhaven2.com/

Robert Klemme

unread,
Sep 4, 2007, 1:32:25 PM9/4/07
to
2007/9/4, Russell Norris <r...@swimcommunity.org>:

> The problem with that is that || doesn't work that way, imo.
>
> x || whatever
>
> only does whatever if x isn't true. Excuse me if I'm sounding like a
> broken record but the more we talk about this the more I'm convinced
> that this "bug" only exists if you expect || to act like + does just
> because it's an "operator" instead of allowing for the fact that ||
> isn't a method like + and friends.
>
> || is special. It takes another bus to ||= than + does, heh. ;)
>
> Sorry for the silliness there.

I wonder why nobody commented on my attempt to point at the practical
implications. Sure I understand that it would be more consistent if
x||=y were equivalent to x = x||y but there are two questions here
IMHO that need to be answered: 1. which solution has advantages in
practice and 2. does it matter at all /in practice/ which approach is
taken?

Kind regards

robert

dbl...@wobblini.net

unread,
Sep 4, 2007, 2:36:25 PM9/4/07
to
Hi --

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.

Sigh. I really wish it were otherwise. What an annoying exception to
the rule.

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.

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.

Rick DeNatale

unread,
Sep 4, 2007, 2:58:22 PM9/4/07
to

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
=> #<D:0xb7b47528>
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?! <G>

7stud --

unread,
Sep 4, 2007, 3:57:23 PM9/4/07
to
>I don't like the idea that the syntactic sugar is
>actually not a reliable drop-in replacement for the thing it's
>sugaring.

Apparently, ||= is sugar free.

dbl...@wobblini.net

unread,
Sep 4, 2007, 6:15:08 PM9/4/07
to

Hi --

On Wed, 5 Sep 2007, Rick DeNatale wrote:

> On 9/4/07, dbl...@wobblini.net <dbl...@wobblini.net> wrote:
>>>
>>> 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.

Right; I got that wrong.

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

OK, then: What an annoying exception to what should be the rule :-)

>> 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}

That one I didn't get wrong :-) I didn't say that retrieving the
default value creates a key (which it doesn't, since the default
value, whether nil or what you set it to, is specifically the default
value for keys that don't exist). My point was that this:

h = Hash.new(1)
h[5] ||= 10

does not map to "x = x", assuming that x stands for h[5]. h[5] = h[5]
*does* set a key; as I said, it would set the 5 key to 1. In fact
this whole thread is really about the fact that hash defaults, which
don't set keys, can be true, which short-circuits the ||= thing. I do
think it's the only such case, and probably fairly edge, though
obviously I'd like to see it do otherwise.

Peña, Botp

unread,
Sep 5, 2007, 12:37:32 AM9/5/07
to
From: Robert Klemme [mailto:short...@googlemail.com]
# 2007/9/4, Yossef Mendelssohn <yme...@pobox.com>:
# > If '"x+=1" will always be "x=x+1"', what's the problem in
# having x ||=
# > 1 always be x = x || 1?
# That an assignment of x=x is useless (basically a nop in the standard
# case of x being a variable).

consider,

irb(main):074:0* h=Hash.new
=> {}
irb(main):075:0> h[5] = h[5]
=> nil
irb(main):076:0> h
=> {5=>nil}
irb(main):062:0* h=Hash.new(1)
=> {}
irb(main):063:0> h[5] = h[5]
=> 1
irb(main):064:0> h
=> {5=>1}

thus h[k] = h[k] could mean
h[k] = h.default or
h[k] = nil

definitely not useless and not noop.

seeking enlightenment -botp


Robert Klemme

unread,
Sep 5, 2007, 5:49:14 AM9/5/07
to
2007/9/5, Peña, Botp <bo...@delmonte-phil.com>:

I said "noop in the standard case of a variable". We can certainly
debate about usefulness or uselessness. I concede that it's not free
of side effects in the case of a Hash, but the only noticeable side
effect is the change of the key set. Assigning with the Hash's
default is not visible through Hash#[] although it is with #fetch
which seems to be rarely used from what I see. So I still say that
usefulness of self assignment is very limited.

Kind regards

robert

dbl...@wobblini.net

unread,
Sep 5, 2007, 6:49:57 AM9/5/07
to

dbl...@wobblini.net

unread,
Sep 5, 2007, 6:51:56 AM9/5/07
to

Peña, Botp

unread,
Sep 5, 2007, 10:06:14 PM9/5/07
to
dBlack wrote:
>It's really always h[k] = h.default -- it's just that the default
>default, so to speak, is nil.

arggh, yes.
i guess my mind was too focused on x=x

irb(main):027:0> zz
NameError: undefined local variable or method `zz' for main:Object
from (irb):27
irb(main):028:0> zz=zz
=> nil

kind regards -botp


Robert Klemme

unread,
Sep 6, 2007, 5:07:32 PM9/6/07
to

I believe it's not the only case. Although x= and []= are different, it
seems in *both* cases assignment is not even invoked for ||=:

irb(main):001:0> class Foo


irb(main):002:1> def x

irb(main):003:2> p "x"
irb(main):004:2> @x
irb(main):005:2> end
irb(main):006:1> def x=(v)
irb(main):007:2> p "x="
irb(main):008:2> @x=v
irb(main):009:2> end
irb(main):010:1> def [](k)
irb(main):011:2> p "[]"
irb(main):012:2> k
irb(main):013:2> end
irb(main):014:1> def []=(k,v)
irb(main):015:2> p "[]="
irb(main):016:2> k
irb(main):017:2> end
irb(main):018:1> end
=> nil
irb(main):019:0> f=Foo.new
=> #<Foo:0x7ff6b5c4>
irb(main):020:0> f.x||=10
"x"
"x="
=> 10
irb(main):021:0> f.x||=20
"x"
=> 10
irb(main):022:0> f.x=30
"x="
=> 30
irb(main):023:0> f[10]||=20
"[]"
=> 10
irb(main):024:0> f[nil]||=30
"[]"
"[]="
=> 30
irb(main):025:0> f[20]=30
"[]="
=> 30
irb(main):026:0>

Kind regards

robert

dbl...@wobblini.net

unread,
Sep 7, 2007, 9:08:39 AM9/7/07
to
Hi --

On Fri, 7 Sep 2007, Robert Klemme wrote:

> On 05.09.2007 00:15, dbl...@wobblini.net wrote:
>>
>> That one I didn't get wrong :-) I didn't say that retrieving the
>> default value creates a key (which it doesn't, since the default
>> value, whether nil or what you set it to, is specifically the default
>> value for keys that don't exist). My point was that this:
>>
>> h = Hash.new(1)
>> h[5] ||= 10
>>
>> does not map to "x = x", assuming that x stands for h[5]. h[5] = h[5]
>> *does* set a key; as I said, it would set the 5 key to 1. In fact
>> this whole thread is really about the fact that hash defaults, which
>> don't set keys, can be true, which short-circuits the ||= thing. I do
>> think it's the only such case, and probably fairly edge, though
>> obviously I'd like to see it do otherwise.
>
> I believe it's not the only case. Although x= and []= are different, it
> seems in *both* cases assignment is not even invoked for ||=:

True, but by "only *such* case" I meant: only case where evaluating
the lhs and getting true doesn't tell the whole story, in terms of the
functionality of the object, because of the defaulting to a value in
the absence of a key.

I suppose one could argue that it isn't ||='s problem if the lhs is
actually a kind of proxy. Nothing has yet convinced me, though, that
||= should not behave like x = x || y, even if the actual assignment
gets optimized away.

Robert Klemme

unread,
Sep 7, 2007, 9:24:21 AM9/7/07
to
2007/9/7, dbl...@wobblini.net <dbl...@wobblini.net>:

> > I believe it's not the only case. Although x= and []= are different, it
> > seems in *both* cases assignment is not even invoked for ||=:
>
> True, but by "only *such* case" I meant: only case where evaluating
> the lhs and getting true doesn't tell the whole story, in terms of the
> functionality of the object, because of the defaulting to a value in
> the absence of a key.

Ah, ok. Should've read more carefully. I am sorry.

> I suppose one could argue that it isn't ||='s problem if the lhs is
> actually a kind of proxy. Nothing has yet convinced me, though, that
> ||= should not behave like x = x || y, even if the actual assignment
> gets optimized away.

Umm, now I am confused. I thought the "optimized away" bit is the
critical bit - if you allow for this difference then they do actually
behave the same, don't they?

Btw, here's a possible explanation why the behavior is the way it is:
[]= and x= are usually costly operations (i.e. not just assignments
but methods doing some work) so avoiding that would help overall
performance. Still I believe that's probably better than the
consistency although I usually tend to favor consistency as well.

Kind regards

robert

dbl...@wobblini.net

unread,
Sep 7, 2007, 9:52:17 AM9/7/07
to
Hi --

On Fri, 7 Sep 2007, Robert Klemme wrote:

> 2007/9/7, dbl...@wobblini.net <dbl...@wobblini.net>:
>>> I believe it's not the only case. Although x= and []= are different, it
>>> seems in *both* cases assignment is not even invoked for ||=:
>>
>> True, but by "only *such* case" I meant: only case where evaluating
>> the lhs and getting true doesn't tell the whole story, in terms of the
>> functionality of the object, because of the defaulting to a value in
>> the absence of a key.
>
> Ah, ok. Should've read more carefully. I am sorry.
>
>> I suppose one could argue that it isn't ||='s problem if the lhs is
>> actually a kind of proxy. Nothing has yet convinced me, though, that
>> ||= should not behave like x = x || y, even if the actual assignment
>> gets optimized away.
>
> Umm, now I am confused. I thought the "optimized away" bit is the
> critical bit - if you allow for this difference then they do actually
> behave the same, don't they?

That may be right. I guess I'm focusing on the visible behavior, and
not the implementation, so I'm probably saying dumb things about the
implementation.

> Btw, here's a possible explanation why the behavior is the way it is:
> []= and x= are usually costly operations (i.e. not just assignments
> but methods doing some work) so avoiding that would help overall
> performance. Still I believe that's probably better than the
> consistency although I usually tend to favor consistency as well.

That was Rick DeNatale's point too: that it could be expensive to call
a =-method. I agree, though if x[1] ||= y were just sugar for x[1] =
x[1] || y, then one could optimize it on the Ruby side with x[1] = y
unless x[1].

Oh well. I don't think this is going to change, and at this point I
should probably stop complaining unless I can come up with a new
implementation :-)

0 new messages