What does the following code mean in Ruby?
||=
Does it have any meaning or reason for the syntax?
||= is a conditional assignment operator
x ||= y
is equivalent to
x = x || y
or alternatively
if defined?(x) and x
x = x
else
x = y
end
To be precise, a ||= b
means "if a
is undefined or falsy (false
or nil
), set a
to b
and evaluate to (i.e. return) b
, otherwise evaluate to a
".
Others often try to illustrate this by saying that a ||= b
is equivalent to a || a = b
or a = a || b
. These equivalencies can be helpful for understanding the concept, but be aware that they are not accurate under all conditions. Allow me to explain:
a ||= b
⇔ a || a = b
?
The behavior of these statements differs when a
is an undefined local variable. In that case, a ||= b
will set a
to b
(and evaluate to b
), whereas a || a = b
will raise NameError: undefined local variable or method 'a' for main:Object
.
a ||= b
⇔ a = a || b
?
The equivalency of these statements are often assumed, since a similar equivalence is true for other abbreviated assignment operators (i.e. +=
,-=
,*=
,/=
,%=
,**=
,&=
,|=
,^=
,<<=
, and >>=
). However, for ||=
the behavior of these statements may differ when a=
is a method on an object and a
is truthy. In that case, a ||= b
will do nothing (other than evaluate to a
), whereas a = a || b
will call a=(a)
on a
's receiver. As others have pointed out, this can make a difference when calling a=a
has side effects, such as adding keys to a hash.
a ||= b
⇔ a = b unless a
??
The behavior of these statements differs only in what they evaluate to when a
is truthy. In that case, a = b unless a
will evaluate to nil
(though a
will still not be set, as expected), whereas a ||= b
will evaluate to a
.
a ||= b
⇔ defined?(a) ? (a || a = b) : (a = b)
????
Still no. These statements can differ when a method_missing
method exists which returns a truthy value for a
. In this case, a ||= b
will evaluate to whatever method_missing
returns, and not attempt to set a
, whereas defined?(a) ? (a || a = b) : (a = b)
will set a
to b
and evaluate to b
.
Okay, okay, so what is a ||= b
equivalent to? Is there a way to express this in Ruby?
Well, assuming that I'm not overlooking anything, I believe a ||= b
is functionally equivalent to... (drumroll)
begin
a = nil if false
a || a = b
end
Hold on! Isn't that just the first example with a noop before it? Well, not quite. Remember how I said before that a ||= b
is only not equivalent to a || a = b
when a
is an undefined local variable? Well, a = nil if false
ensures that a
is never undefined, even though that line is never executed. Local variables in Ruby are lexically scoped.
a ||= b
is equivalent to
a || a = b
and not
a = a || b
because of the situation where you define a hash with a default (the hash will return the default for any undefined keys)
a = Hash.new(true) #Which is: {}
if you use:
a[10] ||= 10 #same as a[10] || a[10] = 10
a is still:
{}
but when you write it like so:
a[10] = a[10] || 10
a becomes:
{10 => true}
because you've assigned the value of itself at key 10
, which defaults to true, so now the hash is defined for the key 10
, rather than never performing the assignment in the first place.
As a common misconception, a ||= b
is not equivalent to a = a || b
, but it behaves like a || a = b
.
But here comes a tricky 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.
If X
does NOT have a value, it will be assigned the value of Y
. Else, it will preserve it's original value, 5 in this example:
irb(main):020:0> x = 5
=> 5
irb(main):021:0> y = 10
=> 10
irb(main):022:0> x ||= y
=> 5
# Now set x to nil.
irb(main):025:0> x = nil
=> nil
irb(main):026:0> x ||= y
=> 10