4

I'm reading "Programming Perl" and ran into a strange example that does not seem to make sense. The book describes how the comma operator in Perl will return only the last result when used in a scalar context.

Example:

# After this statement, $stuff = "three"
$stuff = ("one", "two", "three");

The book then gives this example of a "reverse comma operator" a few pages later (page 82)

# A "reverse comma operator".
return (pop(@foo), pop(@foo))[0];

However to me this doesn't seem to be reverse at all..?

Example:

# After this statement, $stuff = "three"
$stuff = reverse_comma("one", "two", "three");

# Implementation of the "reverse comma operator"
sub reverse_comma {
    return (pop(@_), pop(@_))[0];
}

How is this in any way reverse of the normal comma operator? The results are the same, not reversed!

Here is a link to the exact page. The example is near the bottom.

Daniel Widdis
  • 8,424
  • 13
  • 41
  • 63
tjwrona1992
  • 8,614
  • 8
  • 35
  • 98
  • I would guess it's a typo and should be `(pop(@_), pop(@_))[-1]` or something like that. [You're not the first to be confused by this.](http://computer-programming-forum.com/53-perl/0aa44266bcb822b5.htm) – ThisSuitIsBlackNot Apr 25 '16 at 19:58
  • 1
    As the discussion at hand is about slices, I imagine they are describing `(foo, bar)[0]` as the "reverse comma operator". The choice of `pop()` in assembling the list is, at best, unfortunate. – tjd Apr 25 '16 at 20:01
  • @ThisSuitIsBlackNot: if the subscript is -1, it's the same result as the non-reverse comma operator that keeps the right hand side. With the subscript 0, it keeps the left hand side. – brian d foy Apr 25 '16 at 20:08
  • @briandfoy "it keeps the left hand side" Which is the *last* element of `@_`. – ThisSuitIsBlackNot Apr 25 '16 at 20:12
  • @ThisSuitIsBlackNot: It doesn't matter what @_ is. Whatever expression is on the left if the one that's kept. – brian d foy Apr 25 '16 at 20:14
  • @briandfoy I'm aware of that. The example is misleading because it implies that calling a function `sub reverse_comma { return (pop(@_), pop(@_))[0] }` with two arguments will return the left argument, while in reality, it returns the right argument. – ThisSuitIsBlackNot Apr 25 '16 at 20:17
  • I don't think it implies that at all. It says it acts like a reverse comma operator. You created the rest of the context in a section of one liners that mostly talking about slices. – brian d foy Apr 25 '16 at 20:40
  • 1
    @briandfoy The use of `return` and `@_` implies (strongly, since `return` can only be used in subroutines, `eval`, and `do FILE`) that it's the body of a function. It's not a huge leap to try out the implied function, and then be confused when it returns exactly the same result as the comma operator. It would be much better if the example were simply `('foo', 'bar')[0]`. – ThisSuitIsBlackNot Apr 25 '16 at 20:55

4 Answers4

8

It's a bad example, and should be forgotten.

What it's demonstrating is simple:

  • Normally, if you have a sequence of expressions separated by commas in scalar context, that can be interpreted an instance of the comma operator, which evaluates to the last thing in the sequence.

  • However, if you put that sequence in parentheses and stick [0] at the end, it turns that sequence into a list and takes its first element, e.g.

    my $x = (1, 2, 3)[0];
    

    For some reason, the book calls this the "reverse comma operator". This is a misnomer; it's just a list that's having its first element taken.

The book is confusing matters further by using the pop function twice in the arguments. These are evaluated from left to right, so the first pop evaluates to "three" and the second one to "two".

In any case: Don't ever use either the comma or "reverse comma" operators in real code. Both are likely to prove confusing to future readers.

6

It's a cute and clever example, but thinking too hard about it distracts from its purpose. That section of the book is showing off list slices. Anything beyond slicing a list, no matter what is in the list, is not germane to the purpose of the examples.


You're only on page 82 of a very big book (we literally couldn't fit any more pages in because we were at the limit of the binding method), so there's not much we could throw at you. Among the other list slices examples, there this clever one that I wouldn't use in real code. That's the curse of contrived examples though.

But let's say there were a reverse comma operator. It would have to evaluate both side of the comma. Many answers go right for "just return the first thing". That's not the feature though. You have to visit every expression even though you keep one of them.

Consider this much much advanced version with a series of anonymous subroutines that I immediately dereference, each of which prints something then returns a result:

use v5.10;

my $scalar = (
    sub { say "First"; 35 } -> (),
    sub { say "wantarray is ", 0+wantarray } -> (),
    sub { say "Second"; 27 } -> (),
    sub { say "Third"; 137 } -> ()
    );

The parens are there only for precedence since the assignment operator binds more tightly than the comma operator. There's no list here, even though it looks like there is one.

The output shows that Perl evaluated each even though it kept on the last one:

First
wantarray is 0
Second
Third
Scalar is [137]

The poorly-named wantarray built-in returns false, noting that the subroutine thinks it is in scalar context.

Now, suppose that you wanted to flip that around so it still evaluates every expression but keeps the first one. You can use a literal list access:

my $scalar = (
    sub { say "First"; 35 } -> (),
    sub { say "wantarray is ", 0+wantarray } -> (),
    sub { say "Second"; 27 } -> (),
    sub { say "Third"; 137 } -> ()
    )[0];

With the addition on the subscription, the righthand side is now a list and I pull out the first item. Notice that the second subroutine thinks it is in list context now. I get the result of the first subroutine:

First
wantarray is 1
Second
Third
Scalar is [35]

But, let's put this in a subroutine. I still need to call each subroutine even though I won't use the results of the other ones:

my $scalar = reverse_comma(
    sub { say "First"; 35 },
    sub { say "wantarray is ", 0+wantarray },
    sub { say "Second"; 27 },
    sub { say "Third"; 137 }
    );

say "Scalar is [$scalar]";

sub reverse_comma { ( map { $_->() } @_ )[0] }

Or, would I use the results of the other ones? What if I did something slightly different. I'll add a side effect of setting $last to the evaluated expression:

use v5.10;

my $last;

my $scalar = reverse_comma(
    sub { say "First"; 35 },
    sub { say "wantarray is ", 0+wantarray },
    sub { say "Second"; 27 },
    sub { say "Third"; 137 }
    );

say "Scalar is [$scalar]";
say "Last is [$last]";

sub reverse_comma { ( map { $last = $_->() } @_ )[0] }

Now I see the feature that makes the scalar comma interesting. It evaluates all the expressions, and some of them might have side effects:

First
wantarray is 0
Second
Third
Scalar is [35]
Last is [137]

It's not a huge secret that tchrist is handy with shell scripts. The Perl to csh converter was basically his inbox (or comp.lang.perl). You'll see some shell like idioms popping up in his examples. Something like that trick to swap two numerical values with one statement:

use v5.10;

my $x = 17;
my $y = 137;

say "x => $x, y => $y";

$x = (
    $x = $x + $y,
    $y = $x - $y,
    $x - $y,
    );

say "x => $x, y => $y";

The side effects are important there.

So, back to the Camel, we have an example of where the thing that has the side effect is the pop array operator:

use v5.10;

my @foo = qw( g h j k );

say "list: @foo";

my $scalar = sub { return ( pop(@foo), pop(@foo) )[0] }->();

say "list: @foo";

This shows off that each expression on either side of all the commas are evaluated. I threw in the subroutine wrapper since we didn't show a complete example:

list: g h j k
list: g h

But, none of this was the point of that section, which was indexing into a list literal. The point of the example was not to return a different result than the other examples or Perl features. It was to return the same thing assuming the comma operator acted differently. The section is about list slices and is showing off list slices, so the stuff in the list wasn't the important part.

brian d foy
  • 129,424
  • 31
  • 207
  • 592
  • Wow, its not every day you get to speak directly to the author! :) What exactly would a "reverse comma operator" be then? How is the code in the example `return (pop(@foo), pop(@foo))[0];` supposed to be used to work as expected? – tjwrona1992 Apr 25 '16 at 20:03
  • Just opened my copy of 4th edition and see the example on lines 15 & 16. – tjd Apr 25 '16 at 20:05
  • The slice keeps the left side, but since the left `pop` was evaluated first, you're returning the last element of `@_`. – ThisSuitIsBlackNot Apr 25 '16 at 20:07
  • Okay after the last thing you added that clarifies things a little. So it is *supposed to* act the same as the regular comma operator then? I guess just the fact that the example calls it a "reverse comma operator" when it isn't actually doing the *reverse* of what the comma operator does is what is really confusing. – tjwrona1992 Apr 25 '16 at 20:16
  • One question though? Why wrap it in an anonymous subroutine? That just seems over complicated. `my $scalar = (pop(@foo), pop(@foo))[0]` does in fact evaluate both `pop`s and still returns the result of the first `pop`. – tjwrona1992 Apr 26 '16 at 13:37
  • I merely wanted to preserve the `return` since it's in the book. – brian d foy Apr 26 '16 at 13:41
2

Let's make the examples more similar.

The comma operator:

$scalar = ("one", "two", "three");
# $scalar now contains "three"

The reverse comma operator:

$scalar = ("one", "two", "three")[0];
# $scalar now contains "one"

It's a "reverse comma" because $scalar gets the result of the first expression, where the normal comma operator gives the last expression. (If you know anything about Lisp, it's like the difference between progn and prog1.)

An implementation of the "reverse comma operator" as a subroutine would look something like this:

sub reverse_comma {
    return shift @_;
}
cjm
  • 61,471
  • 9
  • 126
  • 175
  • This misses the important point that the comma operator evaluates all expressions. To keep that, you'd have to go through all of the arguments then return the first one. But, it's not even close to the point of the example. It was just a list slice example. – brian d foy Apr 25 '16 at 21:06
  • 2
    @briandfoy, huh? Perl 5 doesn't have lazy evaluation. The expressions got evaluated when they were placed into `@_`. The sub doesn't need to do anything. – cjm Apr 25 '16 at 22:14
  • I think your example, by using simple scalar values, obscures the point of a scalar comma example with functions. – brian d foy Apr 25 '16 at 22:17
0

An ordinary comma will evaluate its operands and then return the value of the right-hand operand

my $v = $a, $b

sets $v to the value of $b

For the purpose of demonstrating list slices, the Camel is proposing some code that behaves like the comma operator but instead evaluates its operands and then return the value of the left-hand operand

Something like that can be done with a list slice, like this

my $v = ($a, $b)[0]

which sets $v to the value of $a

That's all there is to it really. The book isn't trying to suggest that there should be a reverse comma subroutine, it is simply considering the problem of evaluating two expressions in order and returning the first. The order of evaluation is relevant only when the two expressions have side effects, which is why the example in the book uses pop, which changes the array as well as returning a value

The imaginary problem is this

Suppose I want a subroutine to remove the last two elements of an array, and then return the value of what used to be the last element

Ordinarily that would require a temporary variable, like this

my $last = pop @foo;
pop @foo;
return $last;

But as an example of a list slice the code suggests that this would also work

# A "reverse comma operator".
return (pop(@foo), pop(@foo))[0];

Please understand that this isn't a recommendation. There are a few ways to do this. Another single-statement way is

return scalar splice @foo, -2;

but that doesn't use a list slice, which is the topic of that section of the book. In reality I doubt if the book's authors would propose anything other than the simple solution with the temporary variable. It is purely an example of what a list slice can do

I hope that helps

Borodin
  • 126,100
  • 9
  • 70
  • 144