The ordering operators
Perl has two operators, cmp
and <=>
, which are basically never seen outside of sort
blocks.
That doesn’t mean you can’t use them elsewhere, though. Certainly sort
and these operators were designed to work seamlessly together but there isn’t anything sort
-specific about the operators per se, and in some contexts they can be the most appropriate solution.
An example would be some code that used to go (almost exactly) like this:
my ( $maj, $min, $rel ) = split /[.]/, $version;
my $version_ok = $maj > 3 || (
$maj == 3 && ($min > 6 || (
$min == 6 && $rel >= 8
))
);
Basically, this is a check that the version number of something with a three-component dotted version is at least 3.6.8. As expressed in the code, for both the major and minor version there are two cases where they might be accepted: if the component is above a given threshold then the whole version is acceptable with no further questions, but if equal to that threshold, the version might still be acceptable depending on the value(s) of the lesser component(s). At the same time, if the value is below that threshold, the version is unacceptable, no further questions necessary. So there are three cases, two of which can succeed, and two of which require no further checks.
This is exactly what the <=>
operator offers: if you ask $maj <=> 3
you will get 1 if the major version is above 3 (immediate success), -1 if it is below 3 (immediate failure), and 0 if it is equal to 3 (success to be determined from the lesser components). Because 0 is false, you can chain such comparisons with logical-or, until one of them gives an immediate success/fail – as of course we are used to doing in sort
blocks.
And so this is what the code looks like now:
my ( $maj, $min, $rel ) = split /[.]/, $version;
my $version_ok = ( $maj <=> 3 || $min <=> 6 || $rel <=> 8 ) != -1;
The expression in parentheses has each component of the actual version on the left and the respective component of version 3.6.8 on the right, and will stop at the first comparison that gives a definite answer. The final != -1
then checks that the answer was not “the right side is greater” at the final comparison – i.e. “the given version is not below version 3.6.8”.
I've been Perling since 1997 and I don't think I ever thought about using the comp operators outside of sort() before today. TIL.
Padding numbers and doing comparison would be simpler and straightforward but less clever.
Neat. I do like a nice logical op chain.
I also use it sometimes when there are three different actions based on a comparison, turning something like
into
Mpapec:
Certainly a single comparison using a padded number would be simpler.
Unfortunately the code has no control over the format of
$version
because that value comes from an API. So using a single comparison with padded numbers would require extra logic to munge both values to pad them first. And I can’t think of any even remotely concise and simple way of doing that robustly.Can you?
The best I can come up with is along the lines of
(And even this is simplified vs a fully general solution, because it can rely on the fact that 3.6.8 has only a single digit in each component, so no component of
$version
can be shorter than the corresponding component of 3.6.8.)Now I don’t know about you, but… I’m afraid I’d take the
<=>
version of the code any day over… that. In fact I’d prefer the original (with the chained duplicated numerical comparisons) over that.I would certainly prefer the original for readability in inline code, but I would be OK with the chained comparison operators in a function. Here are couple other version-comparison functions:
Question is why at that point you wouldn’t just write this:
Sure it’s a bit copy-pasty but IMO that’s a benefit rather than a drawback in this case.