Chain comparisons to avoid excessive typing

Checking that a value is between two others involves two comparisons, and so far in Perl that’s meant that you’ve had to type one of the values more than once. That gets simpler in v5.32 with chained comparisons. This would make Perl one of the few languages that support the feature. So far its implemented in v5.31.10 and until v5.32 is actually released, it isn’t a real feature.


If we all went back to the beginning of programming Perl 6Raku has had this feature for a long time, as has Python (but apparently not JavaScript):

So far you have to write two comparisons and join them with a logical operator. If you’ve never complained about this, you probably haven’t been programming long enough:

if( $lower < $n and $n < $upper ) { ... }

Mathematically, you want to write that as lower < n < upper. It's an idea, not an implementation. A high-level language should allow us to use ideas with the compiler translating it into action. And, that's now what you can do. Write the chained comparison:

use v5.31.10;
if( 1 < $ARGV[0] < 10 ) {
	say "$ARGV[0] is between 1 and 10";
	}
else {
	say "Out of range";
	}

The compiler recognizes the idea and turns it into the right lower level steps:

use v5.31.10;
if( 1 < $ARGV[0] and $ARGV[0] < 10 ) {
	say "$ARGV[0] is between 1 and 10";
	}
else {
	say "Out of range";
	}

Evaluating once

In the previous code, it looks like you have to evaluate the middle operand twice since it's part of two comparisons. In a dynamic language like Perl, you can't rely on something keeping the same value. However, Perl caches the first evaluation and uses that value in the second comparison. Consider this program, where you use a function call in the middle. This function returns a possible (probable) different value every time, but also outputs a line every time you call it:

use v5.31.10;

sub unstable {
	my $value = int rand 10;
	say "Called unstable, returning $value";
	return $value;
	}

if( 5 < unstable() < 9 ) {
	say "Found a value between 5 and 9";
	}

The output shows that perl calls the subroutine exactly once:

$ perl5.31.10 ~/Desktop/test.pl
Called unstable, returning 4

$ perl5.31.10 ~/Desktop/test.pl
Called unstable, returning 1

$ perl5.31.10 ~/Desktop/test.pl
Called unstable, returning 5

$ perl5.31.10 ~/Desktop/test.pl
Called unstable, returning 6
Found a value between 5 and 9

This works with a method call too:

use v5.31.10;

package UnstableScalar {
	sub new { bless {}, $_[0] }
	sub val {
		my $value = int rand 10;
		say "Calling val with $value";
		return $value;
		}
	}

my $unstable = UnstableScalar->new;

if( 5 < $unstable->val < 9 ) {
	say "Found a value between 5 and 9";
	}

The output shows that you don't call the method twice:

$ perl5.31.10 ~/Desktop/test.pl
Calling val with 0

$ perl5.31.10 ~/Desktop/test.pl
Calling val with 6
Found a value between 5 and 9

$ perl5.31.10 ~/Desktop/test.pl
Calling val with 5

Broken for tied scalars

As I write this article, tied scalars are evaluated more than once.

use v5.31.10;

package UnstableScalar {
	use parent qw(Tie::Scalar);
	sub TIESCALAR { bless {}, $_[0] }
	sub FETCH {
		my $value = int rand 10;
		say "Fetching scalar with $value";
		return $value;
		}
	}

tie my $unstable, 'UnstableScalar';

if( 5 < $unstable < 9 ) {
	say "Found a value between 5 and 9";
	}

This is basically the same thing with but extra magic going on. If the left side evaluates to false, the second evaluation doesn't happen:

$ perl5.31.10 ~/Desktop/test.pl
Fetching scalar with 1

$ perl5.31.10 ~/Desktop/test.pl
Fetching scalar with 8
Fetching scalar with 4
Found a value between 5 and 9

$ perl5.31.10 ~/Desktop/test.pl
Fetching scalar with 6
Fetching scalar with 9

You already know that a method call works, so you can get the object underneath the tie then call a method on that. This works as you would expect:

	if( 5 < tied($unstable)->FETCH < 9 ) {
		say "Found a value between 5 and 9";
		}

2 thoughts on “Chain comparisons to avoid excessive typing”

  1. Some time ago I have similar idea:

    $x && ne $y

    Which should be expanded as:

    $x && $x ne $y

    Is there similar ideas? Yes! Look at:

    $x += $y

    Which is actually is:

    $x = $x + $y

    Here operator = creates **context** for next operator and actually should be written as:

    $x = + $y

    (or, probably, vice versa: $x ne && $y if we want to fallback as += is written).

    But for visually ‘ne &&’ and ‘&& ne’ are pretty same enough in compare to ‘+=’ and ‘= +’.

    So to cover more wide range of tasks we should use ‘= +’ form.

    More examples to think about:

    $x = ?: $y # similar to $x ||= $y
    $x = ? $y:; # $x = $y if $y

    I even try some implementation to test how this is working in reality

Comments are closed.