This post originated from an RSS feed registered with Ruby Buzz
by Rick DeNatale.
Original Post: x ||= y, Redux
Feed Title: Talk Like A Duck
Feed URL: http://talklikeaduck.denhaven2.com/articles.atom
Feed Description: Musings on Ruby, Rails, and other topics by an experienced object technologist.
A while back there was quite a thread on the Ruby-lang mailing list about the real semantics of the expression.
a[x] ||= some_value
In many books and articles on Ruby the form:
a <op>= b
Where <op> is an operator like + Is described as syntactic sugar:
a = a <op> b
While this is true in most cases, it isn’t’ true if the operator is ||.
This comes to light when the left hand side is a method call, to an accessor, or accessor-like method. For example
h = Hash.new("hello")
h[:fred] ||= ""
h #=> {}
Some find it surprising that the assignment doesn’t cause the hash to have :fred as a key. What this code snippet shows is that the assignment doesn’t actually assign anything if the left hand expression returns a logically true value. A Hash with a default value will return the default value when accessed by any key which is not present in the hash. Since h[:fred] returns the default value, the assignment doesn’t happen.
This affects any object which has ‘accessor’ methods. Here’s a class cooked up just to explore this aspect of Ruby.
class ChattyCathy
def initialize(x=nil)
@x = x
puts "created x is now #{x.inspect}"
end
def x
puts "x read x is #{@x.inspect}"
@x
end
def x=(val)
puts "x written, now #{val.inspect}"
@x = val
end
end
The purpose of this class is simply to let us see exactly when the x attribute is read and written. Now if we run this code
c = ChattyCathy.new(42)
puts "about to evaluate c.x ||= 43"
c.x ||= 43
c = ChattyCathy.new
puts "about to evaluate c.x ||= 43"
c.x ||= 43
We get the following output:
created x is now 42
about to evaluate c.x ||= 43
x read x is 42
created x is now nil
about to evaluate c.x ||= 43
x read x is nil
x written, now 43
Which clearly illustrates just when the assignment actually happens.
The real expansion of x ||= y
Matz explains that the real expansion of x ||= y is:
x || x = y
The expectation that x ||= y is the same as x = x || y, does seem reasonable to someone ‘coming from’ C or one of it’s derivative languages. As far as I can determine, C introduced the notion of assignment operators like += and -=. And K&R defined these assignment operators as a shorthand for x = x + y, etc.
On the other hand, although C has logical operators || and && which, like Ruby have ‘short-circuit’ evaluation, it doesn’t allow ||=, or &&= as assignment operators.
Since || is a ‘short-circuit’ boolean operator, the left hand operand expression is only evaluated if the right hand operand expression evaluates to a logically false value, i.e. either nil or false.
The way that Matz included ||= as an assignment operator makes perfect sense to me. The ||= assignment operator reserves the short-circuit nature of ||.
So what about x &&= y
Although I haven’t seen this discussed anywhere, &&= in Ruby has similar behavior:
c = ChattyCathy.new
puts "about to evaluate c.x &&= true"
c.x &&= true
puts "about to evaluate c.x = \"hi\""
c.x = "Hi"
puts "about to evaluate c.x &&= true"
c.x &&= true
created x is now nil
about to evaluate c.x &&= true
x read x is nil
about to evaluate c.x = “hi”
x written, now “Hi”
about to evaluate c.x &&= true
x read x is “Hi”
x written, now true