Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does print ($a = a..c) produce: 1E0

Tags:

perl

print (a..c) # this prints: abc  
print ($a = "abc") # this prints: abc

print ($a = a..c); # this prints: 1E0

I would have thought it would print: abc

use strict;
print ($a = "a".."c"); # this prints 1E0

Why? Is it just my computer? edit: I've got a partial answer (the range operator .. returns a boolean value in scalar context - thanks) but what I don't understand is: why does: print ($a = "a"..."c") produce 1 instead of 0 why does: print ($a = "a".."c") produce 1E0 instead of 1 or 0

like image 842
Literat Avatar asked Nov 03 '11 23:11

Literat


2 Answers

There are a number of subtle things going on here. The first is that .. is really two completely different operators depending on the context in which it's called. In list context it creates a list of values (incrementing by one) between the given starting and ending points.

@numbers =  1  ..  3;  # 1, 2, 3
@letters = 'a' .. 'c'; # a, b, c (Yes, Perl can increment strings)

Because print interprets its arguments in list context

print 'a' .. 'c';    # <-- this
print 'a', 'b', 'c'; # <-- is equivalent to this

In scalar context, .. is flip-flop operator. From Range Operators in perlop:

It is false as long as its left operand is false. Once the left operand is true, the range operator stays true until the right operand is true, AFTER which the range operator becomes false again.

Assignment to a scalar value as in $a = ... creates scalar context. That means that the .. in print ($a = 'a' .. 'c') is an instance of the flip-flop operator, not the list creation operator.

The flip-flop operator is designed to be used when filtering lines in a file. e.g.

while (<$fh>) {
    print if /first/ .. /last/;
}

would print all of the lines in a file starting with the one that contained first and ending with the one that contained last.

The flip-flop operator has some additional magic designed to make it easy to filter based on the line number.

while (<$fh>) {
    print if 10 .. 20;
}

will print lines 10 through 20 of a file. It does this by employing special case behavior:

If either operand of scalar .. is a constant expression, that operand is considered true if it is equal (==) to the current input line number (the $. variable).

The strings a and c are both constant expressions so they trigger this special case. They aren't numbers, but they're used as numbers (== is a numeric comparison). Perl will convert scalar values between strings and numbers as needed. In this case, both values nummify to 0. Therefore

print ($a = 'a' .. 'c');             # <-- this
print ($a = 0 .. 0);                 # <-- is effectively this
print ($a = ($. == 0) .. ($. == 0)); # <-- which is really this

We're getting close to the bottom of the mystery. On to the next bit. More from perlop:

The value returned is either the empty string for false, or a sequence number (beginning with 1) for true. The sequence number is reset for each range encountered. The final sequence number in a range has the string "E0" appended to it

If you haven't read any lines from a file yet, $. will be undef which is 0 in a numerical context. 0 == 0 is true, so the .. returns a true value. It's the first true value, so it's 1. Because both the left-hand and right-hand sides are true the first true value is also the last true value and the E0 "this is the last value" suffix is appended to the return value. That is why print ($a = 'a' .. 'c') prints 1E0. If you were to set $. to a non-zero value the .. would be false and return the empty string.

print ($a = 'a' .. 'c'); # prints "1E0"
$. = 1;
print ($a = 'a' .. 'c'); # prints nothing

The very final piece of the puzzle (and I might be going too far now) is that the assignment operator returns a value. In this case that's the value assigned to $a1 -- 1E0. This value is what is ultimately spit out by the print.

1: Technically, the assignment produces a lvalue for the item assigned to. i.e. it returns an lvalue for the variable $a which then evaluates to 1E0.

like image 59
Michael Carman Avatar answered Sep 22 '22 09:09

Michael Carman


It's a matter of list context vs. scalar context, as explained in perldoc perlop:

In scalar context, ".." returns a boolean value. The operator is bistable, like a flip-flop, and emulates the line-range (comma) operator of sed, awk, and various editors. Each ".." operator maintains its own boolean state, even across calls to a subroutine that contains it. It is false as long as its left operand is false. Once the left operand is true, the range operator stays true until the right operand is true, AFTER which the range operator becomes false again. It doesn't become false till the next time the range operator is evaluated. It can test the right operand and become false on the same evaluation it became true (as in awk), but it still returns true once. If you don't want it to test the right operand until the next evaluation, as in sed, just use three dots ("...") instead of two. In all other regards, "..." behaves just like ".." does.

[snip]

The final sequence number in a range has the string "E0" appended to it, which doesn't affect its numeric value, but gives you something to search for if you want to exclude the endpoint.

EDIT in response to DanD man's comment:

I find it a bit hard to digest too; frankly, I rarely use the .. operator, and even more rarely in scalar context. But for example, the expression 5..10 in an input loop implicitly compares to the current value of $. (that's part of the description that I didn't quote; see the manual). On lines 5 through 9, it yields a true value (experiment shows that it's a number, but the documentation doesn't say so). On line 10, it yields a number with "E0" appended to it -- i.e., it's in exponential notation, but with the same value it would have without the "E0".

The point of the "E0" tweak is to let you detect whether you're in a specified range and to flag the last line in the range for special treatment. Without the "E0", you wouldn't be able to treat the final match specially.

An example:

#!/usr/bin/perl

use strict;
use warnings;

while (<>) {
    my $dotdot = 2..4;
    print "On line $., 2..4 yields \"$dotdot\"\n";
}

Given 5 lines of input, this prints:

On line 1, 2..4 yields ""
On line 2, 2..4 yields "1"
On line 3, 2..4 yields "2"
On line 4, 2..4 yields "3E0"
On line 5, 2..4 yields ""

This lets you detect whether a line is inside or outside the range and when it's the last line in the range.

But scalar .. is probably more commonly used just for its boolean result, often in one-liners; for example, perl -ne 'print if 2..4' will print lines 2, 3, and 4 of whatever input you give it. It's deliberately similar to sed -n '2,4p'.

like image 31
Keith Thompson Avatar answered Sep 19 '22 09:09

Keith Thompson