Timwi
Timwi

Reputation: 66594

Order of execution with comma operator in Perl

Consider the following script:

print'+'x$z,($z=1,$w)?'':$_ for 1..3;

This prints, as I would expect, 1+2+3. The variable $z is initially unassigned, so '+'x$z evaluates to empty; after that, $z is set to 1, so '+'x$z now evaluates to +.

However, if I change this so that $z contains the + itself:

print$z,($z='+',$w)?'':$_ for 1..3;

the script now prints +1+2+3. This seems to suggest to me that the order of execution is different, but I don’t understand why.

What are the precise rules regarding order of execution that cause these two examples to behave differently? Is the order of execution even well-defined?

Upvotes: 4

Views: 519

Answers (3)

FMc
FMc

Reputation: 42421

Here's my attempt to make sense of your two examples. Consider this script:

use strict;
use warnings;
use Data::Dumper;

sub dd { print Dumper(\@_) }

my $z = 0;

dd($z + 2, ($z = 1));  # Similar to your Version 1.
dd($z,     ($z = 1));  # Similar to your Version 2.

The output, with some comments:

$VAR1 = [
          2,              # The constant 2.
          1               # $z by reference, which prints as 1.
        ];
$VAR1 = [
          1,              # $z by reference.
          ${\$VAR1->[0]}  # Ditto.
        ];

In Version 1, Perl cannot pass $z + 2 directly to dd(). It must evaluate the expression. The result of that evaluation (the constant 2) is passed as the first argument. The second argument is also evaluated: $z is set to 1, the return value of the assignment is $z, and then $z is passed by reference to dd().

In Version 2, Perl can simply pass the first argument directly by reference: no need to evaluate a larger expression. The second argument is the same as in Version 1. The result is that dd() receives same variable twice, as shown in the Data::Dumper output.

Upvotes: 1

tchrist
tchrist

Reputation: 80433

The Original Answer

You need to run this through perl -MO=Deparse,-p. The first bit of code shows this:

print(('+' x $z), ((($z = 1), $w) ? '' : $_)) foreach (1 .. 3);

But the second bit of code shows this:

print($z, ((($z = '+'), $w) ? '' : $_)) foreach (1 .. 3);

Confusticated and Bebothered

Apparently that proved insufficient to sufficiently explain matters to some people. It was not supposed to be, for I had thought it perfectly clear.

The accepted solution erroneously states that this somehow has something to do with the fact that Perl passes scalar variables by implicit reference. It has nothing to with that at all. It is a simple matter of precedence and order of evaluation. I had intended that the Deparse output should make that clear.

Apparently some are still confused.


The First Version

Very well, here’s your explanation all daintied up on silver platter for you.

This:

print'+'x$z,($z=1,$w)?'':$_ for 1..3;

is equivalent, courtesy of Deparse and some extra formating, to this:

{
    ($w, $z) = (undef, undef);
    for (1..3) {
        print(("+" x $z), ((($z = 1), $w) ? "" : $_))
    }
} continue {
    print "\n";
}

Now, unrolling the loop and separating out what happens when produces this:

{
     ($w, $z) = (undef, undef);
    {
        local $_ = 1;
        $temp = "+" x $z; # $z is undef
        $z = 1;
        print $temp, $_;
    }
    {
        local $_ = 2;
        $temp = "+" x $z; # $z is 1
        $z = 1;
        $_ = $_;
        print $temp, $_;
    }
    {
        local $_ = 3;
        $temp = "+" x $z; # $z is 1
        $z = 1;
        $_ = $_;
        print $temp, $_;
    }
} continue {
    print "\n";
}

All three of those produce identical output: 1+2+3.

The Second Version

Now we start again with the original:

print$z,($z='+',$w)?'':$_ for 1..3;

and produce a deparsing version:

{
    ($w, $z) = (undef, undef);
    for (1..3) {
        print($z, ((($z = "+"), $w) ? "" : $_));
    }
} continue {
    print "\n";
}

followed by a loop unroll version:

{
    ($w, $z) = (undef, undef);
    {
        local $_ = 1;
        $z = "+";
        print $z, $_;
    }
    {
        local $_ = 2;
        $z = "+";
        print $z, $_;
    }
    {
        local $_ = 3;
        $z = "+";
        print $z, $_;
    }
} continue {
    print "\n";
}

All three versions, for reasons I REALLY HOPE ARE NOW ABUNDANTLY CLEAR print the same result: +1+2+3.


For Further Enlightenment

The best way to track down what is happening when is to put a trace on it:

tie $z, "Tie::Trace", "z";
tie $w, "Tie::Trace", "w";

($w, $z) = (undef, undef);
print'+'x$z,($z=1,$w)?'':$_ for 1..3;
print "\n";

{
    ($w, $z) = (undef, undef);
    for (1..3) {
        print(("+" x $z), ((($z = 1), $w) ? "" : $_))
    }
} continue {
    print "\n";
}

{
     ($w, $z) = (undef, undef);
    {
        local $_ = 1;
        $temp = "+" x $z; # $z is undef
        $z = 1;
        print $temp, $_;
    }
    {
        local $_ = 2;
        $temp = "+" x $z; # $z is 1
        $z = 1;
        $_ = $_;
        print $temp, $_;
    }
    {
        local $_ = 3;
        $temp = "+" x $z; # $z is 1
        $z = 1;
        $_ = $_;
        print $temp, $_;
    }
} continue {
    print "\n";
}

($w, $z) = (undef, undef);
print$z,($z='+',$w)?'':$_ for 1..3;
print "\n";

{
    ($w, $z) = (undef, undef);
    for (1..3) {
        print($z, ((($z = "+"), $w) ? "" : $_));
    }
} continue {
    print "\n";
}

{
    ($w, $z) = (undef, undef);
    {
        local $_ = 1;
        $z = "+";
        print $z, $_;
    }
    {
        local $_ = 2;
        $z = "+";
        print $z, $_;
    }
    {
        local $_ = 3;
        $z = "+";
        print $z, $_;
    }
} continue {
    print "\n";
}

package Tie::Trace;

sub TIESCALAR {
    my($class, $name, $value) = @_;
    return bless {
        NAME  => $name,
        VALUE => undef,
    } => $class;
}

sub FETCH {
    my($self) = @_;
    my $name = '$' . $self->{NAME};
    my $value = $self->{VALUE};
    print STDERR "[reading value ", defined($value) ? $value : "undef",
            " from $name]\n";
    return $value;
}

sub STORE {
    my($self, $value) = @_;
    my $name = '$' . $self->{NAME};
    print STDERR "[writing value ", defined($value) ? $value : "undef",
            " into $name]\n";
    $self->{VALUE} = $value;
    return $value;
}

When you run that, it produces this rather gratifying output:

[writing value undef into $w]
[writing value undef into $z]
[reading value undef from $z]
[reading value undef from $z]
[writing value 1 into $z]
[reading value undef from $w]
[reading value 1 from $z]
[reading value 1 from $z]
[writing value 1 into $z]
[reading value undef from $w]
[reading value 1 from $z]
[reading value 1 from $z]
[writing value 1 into $z]
[reading value undef from $w]
1+2+3
[writing value undef into $w]
[writing value undef into $z]
[reading value undef from $z]
[reading value undef from $z]
[writing value 1 into $z]
[reading value undef from $w]
[reading value 1 from $z]
[reading value 1 from $z]
[writing value 1 into $z]
[reading value undef from $w]
[reading value 1 from $z]
[reading value 1 from $z]
[writing value 1 into $z]
[reading value undef from $w]
1+2+3
[writing value undef into $w]
[writing value undef into $z]
[reading value undef from $z]
[reading value undef from $z]
[writing value 1 into $z]
[reading value 1 from $z]
[reading value 1 from $z]
[writing value 1 into $z]
[reading value 1 from $z]
[reading value 1 from $z]
[writing value 1 into $z]
1+2+3
[writing value undef into $w]
[writing value undef into $z]
[writing value + into $z]
[reading value undef from $w]
[reading value + from $z]
[writing value + into $z]
[reading value undef from $w]
[reading value + from $z]
[writing value + into $z]
[reading value undef from $w]
[reading value + from $z]
+1+2+3
[writing value undef into $w]
[writing value undef into $z]
[writing value + into $z]
[reading value undef from $w]
[reading value + from $z]
[writing value + into $z]
[reading value undef from $w]
[reading value + from $z]
[writing value + into $z]
[reading value undef from $w]
[reading value + from $z]
+1+2+3
[writing value undef into $w]
[writing value undef into $z]
[writing value + into $z]
[reading value + from $z]
[writing value + into $z]
[reading value + from $z]
[writing value + into $z]
[reading value + from $z]
+1+2+3

Summary

I have now laboriously demonstrated that what is actually happening here as nothing whatsoever to do with pass-by-reference. It has to do with only the order of evaluation alone, and nothing else.

Upvotes: -2

ikegami
ikegami

Reputation: 386501

Arguments are passed by reference in Perl.

print $z, ($z='+',$w) ? '' : $_;

is basically

{
   local @_;
   alias $_[0] = $z;
   alias $_[1] = ($z='+',$w) ? '' : $_;
   &print;
}

Because $_[0] is aliased to $z, changes to $z are reflected in $_[0], even if those changes occur after the argument is evaluated.

You can see the same effect in the following:

my $x = 3;
sub f { 
   ++$x;
   print("$_[0]\n");
}
f($x);  # 4

Upvotes: 4

Related Questions