Ruby Short Circuit (||=) edge case - response
On DABlog.com there is an article on Ruby Short Circuit evaluation. It’s an interesting read, although it comes to a slightly incorrect conclusion.
The questions is what does the statement x ||= 1 expand to. The initial hypothesis was that it expands to x = x || 1. That struck me as odd because my thought was that it expanded to x || x = 1. Rather than rehashing his examples, I’ll assume you’ve read the original article. If you haven’t, I’ll wait.
Now that we’ve all read the article, the conclusion was that x ||= 1 expands to x or x = 1. Again this struck me as odd, why would you take the || operator and convert it to the “or” operator which has lower precedence. So I prepared a little irb session that demonstrates what the actual expansion is:
C:\home>irb
irb(main):001:0> h = Hash.new(1)
=> {}
irb(main):002:0> h[:x] || h[:x] = 2
=> 1
irb(main):003:0> h
=> {}
irb(main):004:0> h = Hash.new
=> {}
irb(main):005:0> val = h[:x] ||= 2
=> 2
irb(main):006:0> val
=> 2
irb(main):007:0> val = h[:y] or h[:y] = 2
=> 2
irb(main):008:0> val
=> nil
irb(main):009:0> val = h[:y] || h[:y] = 2
=> 2
irb(main):010:0> val
=> 2
irb(main):011:0> h
=> {:x=>2, :y=>2}
irb(main):012:0>
The trick is that in ruby “or” has a lower precedence than ||. Unfortunately “or” has a lower precedence than the assignment operator “=”. This example shows that x ||= 1 in fact expands to x || x = 1 rather than x or x = 1.
Edit: It looks like my expansion wasn’t correct either. mernen on reddit posted a code snippet that illustrates the further expansion. It looks like x ||= 1 actually expands to (x || (x = (y))). The parentheses matter because they make “or” versus || a mute point. The code snippet posted by mernen is:
x = nil
y = 1 + x || x = 10 # TypeError: nil can’t be coerced into Fixnum
y = 1 + x ||= 10 # => 11
[...] There was a response to my post from yesterday on Procnew.com, which pointed out that when you’ve got this: [...]