http://qs1969.pair.com?node_id=377450

The Scalar Range Operator

I have been playing with the scalar range operator, and it confused me so much that I started experimenting and reading about it, and here is what I wrote up.

Numeric Values

This operator, in scalar context, has two forms that act as a bistable or flip-flop. The first form looks like this:
if ($left .. $right) { ... }
This if statement works as follows: The condition evaluates false until $left evaluates true. Then the $left condition is ignored, and the condition continues to evaluate true until $right evaluates true, at which point the condition evaluates false, and it goes back to check $left. In this way, it flip-flops between waiting for the left side to evaluate true, and then waiting for the right side to evaluate true. Very strange, until you see it operating in a program:
while (<DATA>) { print if 2 .. 4; } __DATA__ first second third fourth fifth __OUTPUT__ second third fourth
This program prints out the second and third line of the data. A numeric value in the scalar range operator is therefore compared to $..

Regular Expressions

This example shows the use of two regular expressions in the scalar range operator:
while (<DATA>) { print if /start/ .. /end/; } __DATA__ ignore start first second third end ignore start fourth fifth end ignore __OUTPUT__ start first second third end start fourth fifth end
It prints out lines in the data beginning with the line that first evaluates true (start), until the line that next evaluates true (end). All the lines that are not bracked by start/end pairs are ignored. Note that this data contains two blocks of lines that are between start and end markers, and the lines outside those ranges are ignored.

Numeric and Regular Expressions

Combining a numeric and a regex in the range operator also works as expected. In this example, the lines from $. == 1 until $_ =~ /end/ are printed.
while (<DATA>) { print if 2 .. /end/; } __DATA__ first second third end ignore __OUTPUT__ second third end

Exluding Markers

In order to exclude lines that contain start and end, a further condition is required. The condition is that the result returned by the scalar range operator must be neither 1 (representing $. of 1), nor must it contain E. When the operator encounters the line that evaluates true for the right-hand-side, it's return value is (in the example below) 5E0. This number evaluates to 5, but contains that E, which is the indicator that this line terminates the right-hand-side of the operator. This code prints all the lines between the start and end lines:
while (<DATA>) { if (my $num = /start/ .. /end/) { print unless $num == 1 || $num =~ /E/; } } __DATA__ ignore start first second third end ignore start fourth fifth end ignore __OUTPUT__ first second third fourth fifth

To better illustrate that, this program prints out that value, for all lines between start and end:

while (<DATA>) { if (my $num = /start/ .. /end/) { print $num, "\t", $_; } } __DATA__ ignore start first second third end ignore start fourth fifth end ignore __OUTPUT__ 1 start 2 first 3 second 4 third 5E0 end 1 start 2 fourth 3 fifth 4E0 end

Markers on Same Line

This example places both the start and end tokens on the same line. From the output, it can be seen that the combined line has a value of 1E0 which satisfies the test as both the first and last line of the desired input.
while (<DATA>) { if (my $num = /start/ .. /end/) { print $num, "\t", $_; } } __DATA__ ignore start first second end ignore start third end ignore __OUTPUT__ 1 start 2 first 3 second 4E0 end 1E0 start third end

The Scalar ... Operator

The other form of the scalar range operator is .... This operator performs as the .. operator does, but lines that meet one criteria are not also evaluated for the other. So a line that contains both start and end is only evaluated once - in this case for the start line, causing this data to be considered as having a start but not an end, meaning that the data is not properly treated in this example.
while (<DATA>) { if (my $num = /start/ ... /end/) { print "$num\t$_"; } } __DATA__ ignore start first end ignore __OUTPUT__ 1 start first end 2 ignore
This form of the scalar range operator is more efficient only if it is known that both conditions can never be true on the same line.

Update: Changed examples to not include so many edge cases. Thanks to hossman.

Replies are listed 'Best First'.
Re: The Scalar Range Operator
by pg (Canon) on Jul 27, 2004 at 05:41 UTC
    "The condition evaluates false until $left evaluates true. Then the $left condition is ignored, and the condition continues to evaluate true until $right evaluates true, at which point the condition evaluates false, and it goes back to check $left."

    Not very precise, and a bit misleading. In the case of "if (2..4)", $left is 2, and $right is 4, both of them always evalutes to true. I know that later you explained that, they were compared to $., and that was right. I suggest you reword this part to make it correct, and agree with other parts of your post.

    I think it would be useful to give this example, to explain an interesting situation, which people might run into:

    use strict; use warnings; open(FILE, "<", "test.dat"); while (<FILE>) { print if 2 .. 4; } print "=====\n"; open(FILE, "<", "test.dat"); while (<FILE>) { print if 2 .. 4; } ====input==== one two three four five ====output==== two three four =====

    This reminds people the fact that $. is not reset when a file is reopened when no explicit close() called in between.

Re: The Scalar Range Operator
by hossman (Prior) on Jul 27, 2004 at 01:53 UTC

    You might want to consider changing some of your examples so that they don't lie on edge cases (that way it's more obvious what's happening). For example...

    while (<DATA>) { print if 2 .. 3; }

    ...might be more instructive as...

    while (<DATA>) { print if 2 .. 4; }

    Likewise for ...

    while (<DATA>) { print if 1 .. /end/; }

    ...and...

    while (<DATA>) { print if 2 .. /end/; }
Re: The Scalar Range Operator
by denishowe (Acolyte) on Sep 11, 2012 at 13:43 UTC

    Can you use the scalar range operator with map and grep? Sure, but you have to be careful.

    This does what you'd expect:

    grep $_ == 3 .. $_ == 7, 1..10 # ==> (3, 4, 5, 6, 7)
    because grep's first argument is evaluated as a scalar.

    This is very puzzling:

    map $_ == 3 .. $_ == 7, 1..10 # ==> (0, 0, 0, 0, 0, 0, 1, 0, 0, 0)
    until you remember that map's first argument is evaluated as a list. What you meant, of course, was:
    map scalar($_ == 3 .. $_ == 7), 1..10 # ==> (,,1,2,3,4,5E0,,,)
    -- the expected result.

    The puzzling case above gets even more puzzling if we show the list that map returns for each input:

    map $_ == 3 .. $_ == 7, 1 # ==> (0) map $_ == 3 .. $_ == 7, 2 # ==> (0) map $_ == 3 .. $_ == 7, 3 # ==> () map $_ == 3 .. $_ == 7, 4 # ==> (0) map $_ == 3 .. $_ == 7, 5 # ==> (0) map $_ == 3 .. $_ == 7, 6 # ==> (0) map $_ == 3 .. $_ == 7, 7 # ==> (0, 1) map $_ == 3 .. $_ == 7, 8 # ==> (0) map $_ == 3 .. $_ == 7, 9 # ==> (0) map $_ == 3 .. $_ == 7, 10 # ==> (0)
    we get the same ten elements as before, though in an unexpected way. The first line is evaluating:
    1 == 3 .. 1 == 7
    which is
    "" .. ""
    Remember that this is being evaluated in list context (because we forgot the scalar()). So, our scalar range operator has accidentally become a list range operator. The final clue is that the list range operator only does string magic (e.g. ("a".."z")) if the strings are non-empty. Otherwise the arguments are converted to integers, in this case zeros:
    0 .. 0
    which is just a list containing a single zero, as we saw above.

    The other cases are now easy to understand.

    map $_ == 3 .. $_ == 7, 3
    is (1 .. ""), which is converted to (1 .. 0), which gives an empty list. Similarly,
    map $_ == 3 .. $_ == 7, 7
    is ("" .. 1) which gives (0, 1).

Re: The Scalar Range Operator
by PipTigger (Hermit) on May 31, 2016 at 15:32 UTC

    Hi pbeckingham.

    Thank you for posting such an informative explanation!

    After reading and running your examples, I've noticed two minor discrepancies between the descriptions. These might cause some confusion (especially for anyone who can't easily run the examples).

    In your first section on 'Numeric Values' the very next sentence after the program listing says "second and third line of the data" which should include "fourth" also (to match the just preceding __OUTPUT__ block).

    Then in your third section on 'Numeric and Regular Expressions' the second sentence says "the lines from $. == 1" which should be 2 like the $left side of the following range operator.

    I hope that helps a bit.

    PeaceOut - Pip@CPAN.Org
Re: The Scalar Range Operator
by Fengor (Pilgrim) on Nov 28, 2006 at 11:30 UTC
    Personally i found Flipin good, or a total flop? much easier to understand and to follow.

    --
    "WHAT CAN THE HARVEST HOPE FOR IF NOT THE CARE OF THE REAPER MAN"
    -- Terry Pratchett, "Reaper Man"