What Ruby’s ||= (Double Pipe / Or Equals) Really Does
In Rubyists Already Use Monadic Patterns, Dave Fayram made a passing reference to using ||=
to set a variable's value if its value were 'Nothing' (false
or nil
in Ruby). The resulting Reddit quickly picked up on his definition (which was fixed later) and argued about ||=
's true meaning which isn't as obvious as many Rubyists think. This spread to Freenode's awesome #ruby-lang IRC channel where I picked it up.
Abstract (or the TLDR Version)
A common misconception is that a ||= b
is equivalent to a = a || b
, but it behaves like a || a = b
In a = a || b
, a
is set to something by the statement on every run, whereas with a || a = b
, a
is only set if a
is logically false (i.e. if it's nil
or false
) because || is 'short circuiting'. That is, if the left hand side of the ||
comparison is true, there's no need to check the right hand side.
The Common Misconception
a ||= b
being equivalent to a = a || b
is a popular interpretation for two reasons:
- If
a
andb
are both local variables,a = a || b
is a short and natural reflection of the outcome. - Other operators like
+=
and-=
do operate this way (and this standard dates back to C), e.g.:a += b
is equivalent toa = a + b
Do not confuse [op]= with anything related to ||= or &&=. They're entirely different ideas and are implemented entirely different[ly].
Evan Phoenix (of Rubinius fame)
What's happening then, if not a = a || b
?
A Starter for One
Here's a simple example of using a ||= b
:
a = nil b = 20 a ||= b a # => 20
In this case, a ||= b
seems to behave like a = a || b
. As mentioned earlier, this is entirely due to a
and b
both being local variables.
Full Demonstration for Hashes and Arrays
Let's try something more complicated:
h = {} def h.[]=(k, v) puts "Setting hash key #{k} with #{v.inspect}" super end # 1. The standard ||= approach h[:x] ||= 10 h[:x] ||= 20 # 2. The a = a || b approach h[:y] = h[:y] || 10 h[:y] = h[:y] || 20 # 3. The a || a = b approach h[:z] || h[:z] = 10 h[:z] || h[:z] = 20
The output:
Setting hash key x with 10 Setting hash key y with 10 Setting hash key y with 10 Setting hash key z with 10
Note that in the first case, using ||=
, the hash key's value is only set once. Once it becomes logically truthful (i.e. anything other than nil
or false
), h[:x]
is no longer assigned any new values, not even itself.
The second case, using the a = a || b
approach, does result in two assignments (of the same value). The value remains 10 but the syntax forces h[:y]
to assign itself as a value again.
In the last case, the behavior is the same as in the first case, demonstrating that a || a = b
is a more realistic notation.
Note: Exactly the same result occurs if we switch the hash for an array and the keys for integers.
Full Demonstration for Getter/Setter Methods
A similar outcome occurs if we're referring to objects with getter/setter methods (which you may call accessors):
class MyClass attr_reader :val def val=(val) puts "Setting val to #{val.inspect}" @val = val end end # 1. The standard ||= approach obj = MyClass.new obj.val ||= 'a' obj.val ||= 'b' # 2. The a = a || b approach obj = MyClass.new obj.val = obj.val || 'c' obj.val = obj.val || 'd' # 3. The a || a = b approach obj = MyClass.new obj.val || obj.val = 'e' obj.val || obj.val = 'f'
And the output shows off similar behavior to the hash and array example:
Setting val to "a" Setting val to "c" Setting val to "c" Setting val to "e"
Default Hash Values: A Sneaky Edge Case?
Our travels don't end there though. Back in 2008, David Black noticed an edge case with hashes that have default values. If you follow the logic above to the letter, this case will not surprise you, although from a pragmatic point of view, it's curious.
Let's take a look:
hsh = Hash.new('default') hsh[:x] # => 'default' # 1. The standard ||= approach hsh[:x] ||= 10 p hsh # => {} # 2. The a = a || b approach hsh[:y] = hsh[:y] || 10 p hsh # {:y=>"default"} # 3. The a || a = b approach hsh[:z] || hsh[:z] = 10 p hsh # {:y=>"default"}
Hashes with default values act in an.. interesting way, depending on your point of view. Merely accessing a value doesn't mean that the value is reified (made concrete) in the hash itself. The reason for this is that you can assign Procs to a hash's default_proc
in order to perform calculations (or even to set values) when an unset key is accessed. It would be undesirable to avoid this behavior merely because a key was accessed earlier on.
Again, we note that the a || a = b
-style approach gives the result closest to the reality of ||=
.
describe "Conditional operator assignment 'obj.meth op= expr'" do
it "is equivalent to 'obj.meth op obj.meth = expr'" do
RubySpec's variables_spec file
Undefined Variables: Another Tricky Case
In the comments for this post, Vikrant Chaudhary brought up another interesting case:
If a is not defined,
a || a = 42
raises NameError, whilea ||= 42
returns 42. So, they don't seem to be equivalent expressions.
Vikrant Chaudhary
It's lucky I said "behaves like" earlier - phew! But joking aside, Vikrant makes a good point.
This tricky case is a little like the hash case. Something intriguing about how Ruby operates behind the scenes throws a spanner into the works again. That is, a variable assignment, even if not run, immediately summons that variable into being. For example:
x = 10 if 2 == 5 puts x
Even though the first line won't be run, x
will exist on the second line and no exception will be raised. Another nasty one:
x = x puts x
Whoa! Well, a ||= 42
is working in a similar way. Ruby sees the assignment at the parsing stage and creates the variable in a way that it wouldn't with a || a = 42
, even though it ends up behaving like the latter once actual execution occurs.
Further Reading
This appears to have been a popular discussion point in Rubyland over the years, so I would be remiss not to include links to some of the best references:
October 18, 2011 at 2:09 am
Also, on 1.9, it will check if a constant is defined. So this
Foo ||= 42
Translates roughly into this:
October 18, 2011 at 2:15 am
Handy extra detail. Hadn't even thought to try that! :-) Might be a funny^H^H^H^H^Hevil way to define classes ;-)
October 18, 2011 at 6:46 am
if a is _not_ defined, `a || a = 42` raises NameError, while `a ||= 42` returns 42. So, they don't seem to be equivalent expressions.
NOTE: `a || a = 42` will raise NameError only for the first time, second call to same expression `a || a = 42` will return 42. (Ruby 1.8.7).
October 18, 2011 at 6:56 am
I didn't see any why. I only saw more examples of what.
October 18, 2011 at 7:54 am
I demand that this operator is called the "amazed duck" operator from now on.
October 19, 2011 at 1:55 am
Vikrant: I've updated the article with your observation. Thanks!
October 20, 2011 at 1:05 pm
@Peter: Welcome. :-)
October 20, 2011 at 6:41 pm
Is the hash 'edge case' example #3 not wrong? You have the resulting hash matching the = a || b case and not the a || a = case.
Instead of
October 20, 2011 at 10:22 pm
"(which was fixed later)" - is my browser broken? I'm still getting "becomes x = x || y" in the original article. I'm not meaning this as criticism of anyone, I'm just confused.
Another interesting thing about ||= is that it won't produce a warning on uninitialized instance variables, whereas the naive alternatives would:
$VERBOSE = true
# Warning@a ||= 1 # No warning
@b || @b = 2 # Warning
@c = @c || 3
You all know about this because you always use warnings, right? Right??
October 20, 2011 at 10:24 pm
Does &&= behave the same way as ||=, except that it's and rather than or?
October 20, 2011 at 10:42 pm
@hjdivad: I see why I've caused confusion but that was intentional (although not necessarily the best idea). That's the code running all in one go, not refreshing the hash between techniques, so you get an unchanged result from technique 2 (which set that key as shown.. but this is why different keys are used on each technique).
October 21, 2011 at 5:20 am
Thanks Peter. Of course, it's obvious now that I read the code more carefully and you've pointed out what to look for. ^_^
October 21, 2011 at 12:43 pm
If you're looking for a "real" Option monad (in the style of Scala's implementation), checkout the Rumonade gem: https://rubygems.org/gems/rumonade
(full disclosure: I am the author of this gem)
Rumonade also contains a scala-like Either class, whose LeftProject and RightProjection are monads, and monadic extensions of Array.
October 31, 2011 at 9:11 pm
It bums me out that THIS is the most-discussed part of my blog post. Here I am pointing out the correspondence between a very interesting pattern and examples of good ruby code, and all people can do is focus on the pedantic out-of-scope "corrections".