Down the Ruby Mine, Part II: Ruby’s seemingly illogical logical operators

Posted by on August 23, 2019

Hello there, my name’s Sam and I’m one of the Summer 2019 Engineering Interns at FreeAgent. As part of my time here I’m writing a series of blog posts on Ruby language features. If you’re a first time Down the Ruby Mine reader then don’t fret, because the posts aren’t dependant on each other. However, if you are interested, you can find the first post here. Today we’ll be exploring the wondrously strange world of Ruby’s logical operators. 

In a language like Java, logical operators are straightforward, uninteresting constructs. They are typically binary operators that take two parameters and return a boolean (true or false value). Below is an example in Java:

boolean this_is_true = true
boolean this_is_false = false

this_is_true && this_is_false
// false

this_is_true || this_is_false
// true

On first inspection Ruby behaves the same way:

this_is_true = true
this_is_false = false

this_is_true && this_is_false
# false

this_is_true || this_is_false
# true

However, the behaviour of the boolean operators change when we move away from true or false values. Ruby groups all objects into two boolean categories: truthy or falsy, using this property to evaluate boolean expressions. Everything in Ruby is considered truthy except nil and false. So in Ruby, 0 is evaluated as true in a boolean expression. What a crazy world we live in!

this_is_true = 5
this_is_also_true = true
this_is_false = nil

this_is_true && this_is_false
# nil

this_is_true || this_is_false
# 5

this_is_true && this_is_also_true
# true

Ruby’s boolean operators behave spookily with non-boolean arguments

Ruby’s boolean operators do not always return boolean values. As shown above, they return the last argument evaluated in the expression. In the case where an expression is short-circuited (it does not need to evaluate the second argument to know the outcome) the first argument is returned. This can be seen in the second example above: when the first argument of a logical OR is true, the resulting expression is always true so the first argument — 5 — is returned. 

Now that you are a master of Ruby’s boolean expressions we can discuss the hip assignment operators ||= and &&=.

||= is a widely loved assignment operator, used to assign an object to a variable if that variable is empty. To be more specific, it checks if the object on the left hand side is falsy and if it is, assigns it the object on the right hand side. Under the hood it is performing the logical OR operation:

a ||= b

# is equivalent to

a = (a || b)

If object a is falsy then it gets assigned b. If a is truthy then it gets assigned itself (doesn’t change)

Thus, it is commonly used for conditionally assigning to a variable when it evaluates to nil. Be reticent though, with great power comes great responsibility. Remember that the other falsy value in Ruby is false. Therefore, this operator also triggers an assignment when the first argument is false.

The &&= gets less love and attention from the community, yet it still performs its operation dutifully:

a &&= b

# is equivalent to

a = (a && b)

If object a is truthy then it gets assigned b. If a is falsy then it gets assigned itself (doesn’t change)

The inverse of ||=, the &&= operator can be used to assign a variable a new object when it already points to an existing (truthy) object. The most common use of &&= is when assigning an object the result of a method call on itself. By using this operator you can ensure that the object will only be reassigned if it existed in the first place. In general, it is less obvious to find applications for the &&= operator but that makes it all the more special when you do!

Congratulations for making it to the end. With your new-found logical knowledge you can ponder some of humanity’s oldest questions that have troubled philosophers for millennia. I’ll be back with another post in a week or so, so you can tell me what answers you’ve found then!

Leave a reply

Your email address will not be published.