dsb has asked for the wisdom of the Perl Monks concerning the following question:

Yes, another issue with a strange and underestimated operator. Here I thought commas were good for only seperating list elements. Well, I guess in a sense I was right...but I didn't realise how much of an effect the low precedence of the comma operator would have on the outcome of a program. I tried a bunch of different things with assignment operators and print statements to see if I could get the gist of the way the comma operator will behave in different situations.
This all started when I first started using the comma operator in print statements like this:
$foo = "bar"; print $foo, "\n";
At first I didn't know what was going on there, but then I realized that expression was being evaluated as:
That's when I decided to play around a bit to see just what kind of chaos I could cause with the comma operator and different combos of parentheses.

First up was this:

($a,$b,$c) = (1,2,3); # started off easy # does the expected
Everything is in list context here. $a is assigned 1, $b is assigned 2, $c is assigned 3.
$a,$b,$c = (1,2,3); # left side acts weird
The left side of the = uses the last valid option, $c(as if being evaluated in scalar context) and then the right side is evaluated in scalar context returning the last valid option, 3. So $c is assigned 3, $a,$b are both undefined.

The next one was a puzzler:

$a,$b,$c = 1,2,3;
The right side acts the same as in the last example. The left side though, acts kinda strange. With out the parentheses, the left side is no longer a list, so only 1 is evaluated, so $c is assigned 1. I would've though the left side would still evaluated as a list in scalar context returning 3. I'm not really sure why it didn't.

Things got kinda funky when I started messing with print statements.

print(1),print(2),print("\n"); # output # 12 - followed by newlin
I thought that was strange since in that statement the comma is acting almost as a line terminator. That one acts as if it was coded:
print 1; print 2; print "\n";
perlfunc pretty much says "If it looks like a function, it is a function". So, when print is called with its argument in parentheses, it is treated like a function call. Fine. What I thought was weird was that this pseudo-list was entirely executed. That is, I didn't think the print(2) and print("\n") would execute. Remembering how fishy things got last time I removed parentheses, I tried this:
print 1, print 2, print "\n"; # output [blankline] 2111
Interesting. Best as I can figure, that one evaluated to print(1, (print 2,(print "\n")));. So it executes from inside to outside(sort of like simlyfying a math expression). The newline is printed first, then second-innermost print is execute with its 2 arguments,2, and the return value of print "\n" which is 1(or true. Those two values are printed. Then the print outside all parentheses is called with 1 as its first argument and the return value of print 2,(print "\n")(which evaluates to 1) as its second.

The funny part is, I understand why that one worked out the way it did. I don't understand why:

executes like 3 seperate statements. I'm guessing the comma saves the program from a syntax error, but why does it allow the other list items to execute.

Anyway, that's it. I don't know why that damn thing acts like it does sometimes(specifically the examples I gave), so if anyone can shed any light on things that would be good.


Amel - f.k.a. - kel

Replies are listed 'Best First'.
Re: The Comma Operator
by japhy (Canon) on Jul 20, 2001 at 03:21 UTC
    The comma operator is twofold; in scalar context, it evaluates the left argument, then the right argument, and returns the right argument.
    $x = (1 + 3, 5 + 7); # $x gets 12 $y = (pop @r, 1000); # @r gets pop()ed, $y gets 1000
    It has less precedence than =, so:
    $a, $b = 1, 2;
    $a, ($b = 1), 2;
    In any other context, it just separates two expressions.
    foo(), bar(), blat() if bonk(); @a = (1,1,2,3,5,8,13);

    Jeff japhy Pinyan: Perl, regex, and perl hacker.
    s++=END;++y(;-P)}y js++=;shajsj<++y(p-q)}?print:??;

Re: The Comma Operator
by lestrrat (Deacon) on Jul 20, 2001 at 03:05 UTC

    With my limited knowledge on this, I think this is what's happening: First of all the comma operator in scalar context evaluates the left side, throws it away, and then return the right side. ie.

    $a, $b, $c = 1, 2, 3; the order of evaluation is (((( $a ), $b ), $c = 1 ), 2), 3) so... 1) $a gets evaluated 2) $b gets evaluated 3) $c = 1 gets evaluated 4) 2 gets evaluated 5) 3 gets evaluated 6) return 3

    Same thing for print:

    print( 1 ), print(2), print("\n" ); 1) print (1 ) gets evaluated 2) print ( 2 ) gets evaluated 3) print ( "\n" ) gets evaluated 4) return result of print ("\n")

    P.S. -- I'm taking an educated guess here, so feel free to correct me if I'm wrong... :-)

Re: The Comma Operator
by petral (Curate) on Jul 20, 2001 at 11:48 UTC
    I once worked out this convoluted way to access time only once per reading without using a temporary.
    perl -lwe'$oldtime = $^T; print( -(($oldtime+0) - ($oldtime = time)) ) while sleep 1'
    (Not to be obscure: the normal way to do this is something like:my $tm = time;  print $tm - $oldtm;  $oldtm = $tm; The only complicating factor being that you don't want to call time() twice since it may roll over in between.  In order to avoid using a temporary, the old value of oldtime must be put into the calculation before oldtime is set to its new value which must also be used. A substraction requires that its arguments be ordered, and sub-expressions for both parts seems to work.)

    But note well, the docs often note that the order of evaluation of arguments to functions is not defined. (This is a long-time caveat carried over from c.) For instance, the above can easily be broken:( -(($oldtime || $^T) - ($oldtime = time)) ) returns a stream of -0's!

    Udpate: And here's a fibonacci series that depends on $b being returned *before* it is updated.
    perl -e'$\=$,=$";$b=1; print $b, $a += $b += $a while $a <1000; print"\n"'
    Update the second: Note also, that any halfway ambitious optimizer could reduce -( (x + 0) - (x = y) ) to (x = y) - (x) to x - x to 0 and produce:print 0;

Re: The Comma Operator
by da (Friar) on Jul 20, 2001 at 19:24 UTC
    This is an excellent application for Deparse. The -p parameter to Deparse gives you nearly as many parenthesis as perl can give you, which is useful for explainging things. I was first exposed to this trick by Brother MeowChow when I asked how in hell his .sig worked.

    > perl -MO=Deparse,-p -e '$d, $e, $f = 1,2,3;' -e syntax OK ($d, $e, ($f = 1), '???', '???');
    The latter arguments are thrown away because the '=' binds more tightly than the comma. If these were subroutines, they would have evaluated before being discarded.
    > perl -MO=Deparse,-p -e 'print(1),print(2),print("\n");' -e syntax OK (print(1), print(2), print("\n"));
    Note the parens around everything; to answer your question about why it evaluates all three terms, it is a list that gets discarded after evaluating its elements, which look like functions so they're evaluated as functions.
    > perl -MO=Deparse,-p -e 'print 1, print 2, print "\n";' -e syntax OK print(1, print(2, print("\n")));
    And your description was exactly right.

    ___ -DA > perl -MPOSIX -le '$ENV{TZ}="EST";print ctime(1000000000)' Sat Sep 8 20:46:40 2001
Re: The Comma Operator
by PetaMem (Priest) on Jul 20, 2001 at 19:00 UTC
    And - as was pointed out not long ago - donīt use $a and $b
    variables as this could add more weirdness to your tests.
    (naming your program or module test could do also... :-))


Re: The Comma Operator
by kael (Monk) on Jul 21, 2001 at 10:10 UTC
    With the comma list of prints your just making a list and ignoring it's acutal value
    think about this
    valid code, right? now how bout this?
    Again valid code, it doesn't do anything but it's valid and will get evalated. Code doesn't HAVE to do something. Now if you can also do
    This will set @a to the return values of joe and someothersub, your doing the same thing with print
    (print("hello"),print "\n");
    This is also valid code, calls both functions puts their return values into a list, and then forgets the list.
    (feel free to correct me if I'm wrong)