Ruby Weekly is a weekly newsletter covering the latest Ruby and Rails news.

What Ruby’s ||= (Double Pipe / Or Equals) Really Does

By Peter Cooper / October 18, 2011

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:

  1. If a and b are both local variables, a = a || b is a short and natural reflection of the outcome.
  2. Other operators like += and -= do operate this way (and this standard dates back to C), e.g.: a += b is equivalent to a = 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, while a ||= 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:

Comments

  1. Konstantin Haase says:

    Also, on 1.9, it will check if a constant is defined. So this

    Foo ||= 42

    Translates roughly into this:

    if defined? Foo
      Foo || Foo = 42
    else
      Foo = 42
    end
  2. Peter Cooper says:

    Handy extra detail. Hadn't even thought to try that! :-) Might be a funny^H^H^H^H^Hevil way to define classes ;-)

  3. Vikrant Chaudhary says:

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

  4. Anne Ominous says:

    I didn't see any why. I only saw more examples of what.

  5. kikito says:

    I demand that this operator is called the "amazed duck" operator from now on.

  6. Peter Cooper says:

    Vikrant: I've updated the article with your observation. Thanks!

  7. Vikrant Chaudhary says:

    @Peter: Welcome. :-)

  8. hjdivad says:

    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.

    # 3. The a || a = b approach
    hsh[:z] || hsh[:z] = 10
    p hsh # {:y=>"default"}

    Instead of

    ruby-1.9.2-p290 :032 > hsh = Hash.new('default')
    => {}
    ruby-1.9.2-p290 :033 >
    ruby-1.9.2-p290 :034 > hsh[:z] || hsh[:z] = 10
    => "default"
    ruby-1.9.2-p290 :035 > p hsh
    {}

  9. Andrew Grimm says:

    "(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
    @a ||= 1 # No warning
    @b || @b = 2 # Warning
    @c = @c || 3
    # Warning

    You all know about this because you always use warnings, right? Right??

  10. Andrew Grimm says:

    Does &&= behave the same way as ||=, except that it's and rather than or?

  11. Peter Cooper says:

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

  12. hjdivad says:

    Thanks Peter. Of course, it's obvious now that I read the code more carefully and you've pointed out what to look for. ^_^

  13. Marc says:

    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.

  14. KirinDave says:

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

Other Posts to Enjoy

Twitter Mentions