assigning to hash keys when there is a default value?

7

7stud --

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

dblack

Hi --

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

7stud --

unknown said:
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.
 
D

dblack

Hi --

unknown said:
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.

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


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

Robert Klemme

2007/9/3 said:
Hi --

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

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
 
7

7stud --

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

7stud --

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

dblack

Hi --

2007/9/3 said:
Hi --

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, #<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 :)


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

dblack

--1926193751-1236775918-1188819428=:20075
Content-Type: MULTIPART/MIXED; BOUNDARY="1926193751-1236775918-1188819428=:20075"

This message is in MIME format. The first part should be readable text,
while the remaining parts are likely unreadable without MIME-aware tools.

--1926193751-1236775918-1188819428=:20075
Content-Type: TEXT/PLAIN; charset=X-UNKNOWN; format=flowed
Content-Transfer-Encoding: QUOTED-PRINTABLE

Hi --

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=3D>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 ||=3D 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 ||=3D will no= t
create new keys with the default value.

I don't have the book at hand but the default value definitely doesn't
mean that all keys exist (or even "exist" :)

irb(main):003:0> h =3D Hash.new(5)
=3D> {}
irb(main):004:0> h[1]
=3D> 5
irb(main):005:0> h.has_key?(1)
=3D> false

The only possible explanation I can come up with, which I don't like,
is that:

h[2] ||=3D 10

is being treated like:

5 =3D 5 || 10

which is just another can of worms.


David

--=20
* 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)
--1926193751-1236775918-1188819428=:20075--
--1926193751-1236775918-1188819428=:20075--
 
7

7stud --

unknown said:
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 ||=.
 
S

Sebastian Hungerecker

Russell said:
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.
 
D

dblack

Hi --

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.


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

Morton Goldberg

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
 
D

dblack

Hi --

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.


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

dblack

Hi --

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.


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

Robert Klemme

2007/9/3 said:
Hi --



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
 
E

Eric Promislow

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
 
P

Peña, Botp

RnJvbTogc2NvbmRzQGdtYWlsLmNvbSBbbWFpbHRvOnNjb25kc0BnbWFpbC5jb21dIE9uIEJlaGFs
ZiBPZiBSdXNzZWxsIE5vcnJpczoNCiMgSSBsZWFybmVkIHRoYXQgeCB8fD0geSBtZWFucyBzZXQg
eCB0byB5IHVubGVzcyB4LCBzbyBJIGRvbid0IHNlZSB0aGUNCg0KdGhlcmUncyB0aGUgYnVnOiB5
b3UndmUgY2hhbmdlZCB0aGUgbWVhbmluZyBvZiB4IHx8PSB5LCBvciBvZiBmb288b3A+PWJhciBm
b3IgdGhhdCBtYXR0ZXIuDQoNCiMgYnVnLiBhbmQgaSBkb24ndCBzZWUgdGhpcyBhcyBiZWluZyBh
dCBhbGwgdW5wcmVkaWN0YWJsZS4NCg0KeW91IGp1c3Qgc2FpZCB5b3UndmUgX2xlYXJuZWQgdGhh
dCB4fHw9eSBtZWFucyBzZXQgeCB0byB5IHVubGVzcyB4LiBTdXJlbHkgaXQgd2FzIF91bnByZWRp
Y3RhYmxlIGF0IHNvbWUgcG9pbnQuIFN1cmVseSB4PG9wPj15IGlzIGEgbm8tYnJhaW5lciBmb3Ig
YSBtYW55IG9mIHVzIDopDQoNCmFuZCBub3cgd2UgYWxsIGhhdmUgdG8gdXBkYXRlIG91ciB0ZXN0
IGNhc2VzLCBjYXVzZSBtYXliZSB4ICs9IDEgbWF5IG5vdCBhbHdheXMgYmUgeCA9IHggKyAxDQoN
CnNwZWFraW5nIG9mIGxlYXN0IHN1cnByaXNlIDooDQoNCmtpbmQgcmVnYXJkcyAtYm90cA0KDQoN
Cg0K
 
R

Robert Klemme

2007/9/4 said:
From: (e-mail address removed) [mailto:[email protected]] On Behalf Of Russell Nor= ris:
# I learned that x ||=3D 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 ||=3D y, or of foo<op>= =3Dbar for that matter.

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

you just said you've _learned that x||=3Dy means set x to y unless x. Sur=
ely it was _unpredictable at some point. Surely x said:
and now we all have to update our test cases, cause maybe x +=3D 1 may no= t always be x =3D x + 1

speaking of least surprise :(

No, "x+=3D1" will always be "x=3Dx+1".

robert
 
Y

Yossef Mendelssohn

2007/9/4 said:
From: (e-mail address removed) [mailto:[email protected]] On Behalf Of Russell N= orris:
# I learned that x ||=3D 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 ||=3D y, or of foo<op>=
=3Dbar for that matter.
# bug. and i don't see this as being at all unpredictable.
you just said you've _learned that x||=3Dy means set x to y unless x. S=
urely it was _unpredictable at some point. Surely x said:
not always be x =3D x + 1


No, "x+=3D1" will always be "x=3Dx+1".

robert

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

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,055
Latest member
SlimSparkKetoACVReview

Latest Threads

Top