Both Perl and Ruby provide two sets of logical operators:
- !, && and || (inherited from C)
- not, and and or
Unfortunately, these two sets of operators are not isomorphic – due to differences in precedence, their semantics are different in subtle ways which make the whole issue of using them error prone. In this article I will try to elucidate this topic.
So what's the difference between them?
In order to be able to disambiguate complex expressions, parsers of programming languages assign precedence for all operators. The best known example is probably the precedence of * being higher than that of + to allow commonly understood mathematical expressions without forcing to use lots of parentheses. Another good example is the precedence of assignment being lower than the precedence of all mathematical operators. This allows statements like
$a = $b + $c; to be parsed correctly without parentheses1.
In Perl and in Ruby, the precedence of !, && and || is higher than the precedence of assignment, while the precedence of not, and and or is lower.
For example, the following code in Perl:
$a = $b || 5;
Will assign the value of
$b evaluates to true, and 5 when
$b evaluates to false (
undef, 0 or an empty string). In short, it works as expected. However, the following code:
$a = $b or 5;
Is completely broken and will just generate a warning, because it is parsed as
($a = $b) or 5;.
Why the low precedence ?
As the example above shows, the low precedence of or is not such a good idea. So why was it made this way ? I think that Larry Wall wanted to make the following code legal Perl that behaves as expected:
open my $fh, "<", $filename or die $!;
Perl allows to drop parentheses where the code is not ambiguous, and proponents of the language believe it makes some code more readable. The code section above will work correctly. With ||, on the other hand, it wouldn't have worked since the precedence of || is higher than that of the comma, and the expression would be parsed as:
open my $fh, "<", ($filename || die $!);
Which isn't what we wanted. In Ruby, where code like the above is not written, this motivation plays no role.
Dealing with the confusion
So it appears Perl and Ruby have two sets of almost identical operators which behave differently sometimes. Isn't this confusing ?
It definitely is. In fact, many "good programming style" guides deal with this confusion in a very strict manner. For instance, Damian Conway's excellent "Perl Best Practices" book advises to avoid the low precedence operators at all, except for the single case of:
open my $fh, "<", $filename or die $!;
In Ruby this is irrelevant, so to extend his advice, it probably isn't recommended to use these operators at all in Ruby, is it ? I personally hold a different view.
In defence of the low precedence operators
In my opinion, the low precedence operators are better, because of two main reasons:
- Not everyone came to Perl and Ruby from C and C++. On the contrary, as the time goes on, less and less people come with this background, and many start from other languages (in lots of cases, from Perl and Ruby themselves). But even for a seasoned C hacker, IMHO
if not client.done? or client.result > 0is more intelligible than
if !client.done? || client.result > 0, since it is more English-like.
- An important factor, at least for me, is the speed and comfort of typing on the keyboard. Typing too many punctuation characters is both slower and more stressful on the wrists. Using not, and and or wherever possible instead of !, && and || definitely helps me put as little stress as possible on my tendons.
But...However, as we have seen, some things just can’t be done with the low precedence operators without resorting to parentheses. So for the singular case of the short-circuit assignment perhaps using || is better than or (Ruby code in this case, although it of course applies to Perl as well):
a = b || 5;Implementing the same functionality with or feels a little superfluous:
a = (b or 5);And || can also be used in the occasionally useful idiom2:
var ||= 5;
or has no answer in this case.
I’m sure, however, that there is much more logical condition code that short-circuit assignment code, so the low precedence operators can be used most of the time.
Another little gotchaThere is another little gotcha about low precedence operators which is mostly relevant for people with C background. or and and have the same precedence (unlike ||, which has a lower precedence than &&), so writing code like:
if cond1 and cond2 or cond3 and cond4
Won’t do what you expected. However, it is a good practice to use parentheses in all complex conditions of this kind anyway, so I don’t see it as a big problem.
Perl and Ruby have two sets of logical operators. These two sets have slightly different semantics, but the confusion can be avoided with a disciplined application of good programming practices. My advice is to use the low precedence operators (not, and and or) all the time. The single exception can be made in the case of short-circuit assignment, where || can be used instead of or (although or can also be used, with help from a couple of parentheses).
1 Had the precedence of assignment been higher than the precedence of addition, this statement would be understood as
($a = $b) + $c;.
2 This translates to
var = var || 5, or “if
var is defined, let it keep its value, otherwise assign 5 to it”.