The &&
and and
operators might both perform logical AND operations, but they are not interchangeable. In fact, they have different precedences:
foo = 3 && foo # foo => nil
foo = 3 and foo # foo => 3
The above example demonstrates how the assignment operation (=
) is performed after the &&
operation and before the and
operation. But why?
Different purposes
In Ruby, the &&
operator is intended for logical operations, but the and
operator is intended to alter control flow. Let's highlight this by adding parentheses to show the order of operations that occurred in the intro example:
foo = (3 && foo)
(foo = 3) and (foo)
In the first expression, the purpose of the &&
is to confirm that the operands on both the left and right sides are truthy. With the right-side foo
undefined, the parenthetical expression evaluates to nil
. This is then assigned to foo
by the =
operator.
However, in the second expression, the and
ensures that the left-side operation completes before the right-side operation begins. Its purpose is to compose (i.e. chain) a string of commands that should run serially until one command in the chain fails:
command1 and command2 and command3 and command4
resource = fetch_resource and parse_resource(resource)
If a command in the sequence fails (e.g. the resource fails to be fetched), the and
short-circuits and doesn't attempt any commands that follow (e.g. parsing the resource). This is how the and
manages control flow.
That said, if a chain consists of only two commands, it's more idiomatic Ruby to use the if
modifier instead:
cmd2 if cmd1
puts "fetch succeeded" if resource = fetch_resource
It's not just and
Just as and
is different from &&
, so are or
and ||
different, and !
and not
too.
In the case of or
, the operator short-circuits after the first command in the chain to successfully complete. It might be used like this:
foo = fetch_from_cache or fetch_from_database or create_new_resource
Here, the fetch_from_database
and create_new_resource
commands are not called if fetch_from_cache
returns a truthy value.
When only two commands need to be chained by or
, it's more idiomatic Ruby to use the unless
modifier instead:
foo = fetch_from_database unless fetch_from_cache
Now that we've walked through examples for and
and or
, the formulation of examples distinguishing not
from !
is left as an exercise to the reader. (I had to do it! I'm married to a mathematician!)
Precedence matters
The reasoning is now understood, but there's one last important thing to note.
Even though the and
and or
operators have the same precedence and are evaluated in expressions from left to right, the &&
and ||
operators are different! Use parentheses when necessary to clarify or enforce the desired order of operations.
true || true && false # => true
(true || true) && false # => false
true or true and false # => false
For reference, here is a subset of the Ruby operators sorted in order of precedence from highest to lowest:
Operator | Description |
---|---|
! |
logical NOT |
&& |
logical AND |
|| |
logical OR |
not |
negation |
and , or |
composition |
if , unless |
modifiers |
Now that that's all sorted, it's time for me to get back to a programming language that uses either one of and
or &&
but not both...