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:
print($foo,"\n");
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:

print(1),print(2),print("\n");
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.

Thanks.

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;
    means
    $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;
      p

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... :-))

    Ciao

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
    @a=(foo,bar,bah);
    valid code, right? now how bout this?
    (foo,bar,bah);
    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
    @a=(joe(),someothersub());
    This will set @a to the return values of joe and someothersub, your doing the same thing with print
    (joe(),someothersub());
    or
    (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)