Missing at point blank range
Tags: perl
Published: 2024-01-14 21:11:47

The temperature here this afternoon is -7&deg;F (-21&deg;C), so it's a good day to stay indoors and procrastinate by browsing Perl blogs; but then, what day isn't? I ran into this [list of top questions on Stackoverflow](https://niceperl.blogspot.com/2023/12/20-best-perl-questions-at-stackoverflow.html), which led me to this bit of [trivia about the range operator](https://niceperl.blogspot.com/2023/12/20-best-perl-questions-at-stackoverflow.html).

That got me thinking about the ways that simple variations in the range operator lead to fun ways to shoot yourself in the foot.  Here's an easy opener. You're going to index an array:
```perl
my @m = qw(a b c d);
say $m[$_] for 1..3;
```
But the coffee kicks in and you type too fast and accidentally end up with
```perl
say $m[$_] for 1.3;
```
That's not the list `(1,2,3)` -- that's the list containing a single number `(1.3)`. Can you use a floating number as an index into an array?  Perl says, "Meh, why not?", helpfully truncates 1.3 to 1, and hands you 'b' as the output.

Now normally, I like to add white space for readability, so I would write that statement as
```perl
say $m[$_] for 1 .. 3;
```
But my fingers are getting kind of spastic (is it the coffee or neural degeneration?  Let's not think about it, la la la), and those readable little spaces start straying. Suppose I make the same error, turning `..` into a single `.`
```perl
say $m[$_] for 1 . 3;
```
Did my white space save me?  Nope. this becomes the list of one element `(13)` -- because string concatenation -- and at run-time, that's a warning for accessing an undefined value by indexing an array out of bounds.

Normally, we enjoy dunking on Python for letting white space change our programs, but maybe Perl needs to just quietly meditate on humility this frosty afternoon. What are the outputs of this little program?
```perl
use v5.38;
my @a;
@a = 1.3;       printf"%10s", "1.3: "; say "(@a)";
@a = 1..3;      printf"%10s", "1..3: "; say "(@a)";
@a = 1 ..3;     printf"%10s", "1 ..3: "; say "(@a)";
@a = 1. .3;     printf"%10s", "1. .3: "; say "(@a)";
@a = 1.. 3;     printf"%10s", "1.. 3: "; say "(@a)";

@a = 1...3;     printf"%10s", "1...3: "; say "(@a)";
@a = 1 ...3;    printf"%10s", "1 ...3: "; say "(@a)";
@a = 1. ..3;    printf"%10s", "1. ..3: "; say "(@a)";
@a = 1.. .3;    printf"%10s", "1.. .3: "; say "(@a)";
@a = 1... 3;    printf"%10s", "1... 3: "; say "(@a)";

# @a = 1 . ..3; printf"%10s", "1 . ..3: "; say "(@a)";
@a = 1 .. .3;   printf"%10s", "1 .. .3: "; say "(@a)";
@a = 1. . .3;   printf"%10s", "1. . .3: "; say "(@a)";
@a = 1. .. 3;   printf"%10s", "1. .. 3: "; say "(@a)";
```
* `1.3` -- See above
* `1..3` -- That's exactly what we expect, the list `(1,2,3)`
* `1 ..3` -- Still parses as we expect, the list `(1,2,3)`
* `1. .3` -- The first surprise.  How is this even valid code? This is the string "13". `1.` becomes the number 1; `.3` parses as a string concatenation operator and the number 3. It's equivalent to `"1" . "3"`
* `1.. 3` -- Back to what we expect, `(1,2,3)`
* `1...3` -- Three dots are just as good as two, see _Range operators_ in [perldoc perlop](https://perldoc.perl.org/perlop#Range-Operators)
* `1 . ..3` -- This one is commented out, because it's a syntax error. Even Perl, in it's highly developed desire to _Do What I Mean_, can't figure out a meaningful way to parse this.
* `1 ...3` -- Still good; (1,2,3).
* `1 .. .3` -- Second surprise: an empty list.  Why? The end of the range (0.3) is less than the beginning.
* `1. . .3` -- If you're going to miss one, it's probably going to be this one. Ready? The answer is "10.3". The first and third dots have become decimal points, and the middle one is a string concatenation.  But `1.` becomes the integer 1, while `.3` becomes a floating value with the string representation "0.3".
* `1. .. 3` -- A somewhat predictable (1,2,3).

There are other combinations of four or more dots and blanks that actually work, in the sense that they aren't syntax errors. How many dots and spaces can we put consecutively? The longest valid string I could come up with is: `@a = 1. ... .3`

Combining range operators doesn't make sense.  For example `@a = 1 .. 3 .. 15` is nonsense, and Perl tells us so with a syntax error.  But add a couple of parentheses and it's no longer a syntax error: `@a = (1 .. 3) .. 15`.  Fortunately it dies in an obvious way at run time.  The second `..` has now transmogrified into a flip-flop operator, which is a whole different blog post.

But how about this abomination: `@a = 1 . 3 .. 15`.  That's the sequence `(13,14,15)` because `1 . 3` is a string concatenation.

And here's a little dessert bite to round out our excursion into dotted Perl.  What if we skip the range and just point-blank use the operator? What's this?
```perl
{ ... }
```
That's the [ellipsis operator](https://perldoc.perl.org/perlsyn#The-Ellipsis-Statement), added to Perl in 5.12 to allow a syntactically valid placeholder for incomplete code. So you can shoot yourself in the foot later.