9

There are a few things I "know" about Perl:

  • lists flatten
  • functions take and return lists

So if I have this:

sub my_test {
  my @x = qw(a b c);
  my @y = qw(x y z t);
  return (@x, @y); 
} 

say my_test; # a b c x y z t
say scalar my_test;

I expect either of two result values:

  • 7, because that's how many items there are in the list qw(a b c x y z t). Indeed, this is what I get from scalar sub { @{[ qw(a b c x y z t) ]} }->().
  • 't', because if you interpret the commas as the comma operator (sigh) you get ('a', 'b', 'c', 'x', 'y', 'z', 't') which evaluates to 't'. Indeed, this is what I get from scalar sub { qw(a b c x y z t) }->().

What you do get instead is… 4, without warning. Why did I get a mix of list flattening and comma operator?

Similar story with hashes and this rather popular pattern:

sub default_override_hash {
  my %defaults = (foo => 'bar', egg => 'quuz');
  my %values = @_;
  my %overrides = (__baz => '');
  return (%defaults, %values, %overrides);
}

scalar default_override_hash; # '1/8'

How does scalar know that default_override_hash returned three hashes, and it should not only just get %overrides (and not everything, and not ''), but its scalar representation as a hash?

melpomene
  • 84,125
  • 8
  • 85
  • 148
badp
  • 11,409
  • 3
  • 61
  • 89

1 Answers1

7

The most important point is: Lists don't flatten. (Lists are flat, but they don't flatten, because to do that they'd have to be nested first.)

, (the comma operator) in list context is list concatenation. A , B in list context evaluates A and B in list context, then concatenates the results.

A , B in scalar context works like C (or JavaScript): It evaluates A in void context, then evaluates (and returns) B in scalar context. (A , B in void context works the same, but evaluates B in void context too.)

In return X, the context of X is the context of the function call itself. Thus sub my_test { return X } scalar my_test is like scalar X. It's like return dynamically looks at the context the current sub call is in, and evaluates its operand expression accordingly.

perldoc perlsub says:

A return statement may be used to exit a subroutine, optionally specifying the returned value, which will be evaluated in the appropriate context (list, scalar, or void) depending on the context of the subroutine call.)

As described above, @x, @y in scalar context evaluates @x in void context (which for an array does nothing), then evaluates and returns @y in scalar context. An array in scalar context yields the number of elements it contains.

The same logic applies to %defaults, %values, %overrides. , is left-associative, so this parses as (%defaults , %values) , %overrides. This evaluates %defaults , %values in void context (which in turn evaluates %defaults, then %values in void context, which has no effect), then evaluates and returns %overrides in scalar context. A hash in scalar context returns a (rather useless) string describing hash internals.

melpomene
  • 84,125
  • 8
  • 85
  • 148
  • The bolded sentence is wrong. `return` doesn't look at the context at all. Even if it did, `return` is evaluated *after* the comma operator, so what it does at run-time couldn't possibly have any effect on the comma operator. That means it's completely wrong to say that `return` is the one that checks the caller's context (or that it's like it did); it's the comma operator that does that. – ikegami Jul 25 '16 at 00:11
  • The correct wording would be: **The comma operator (`,`) dynamically looks at the context in which the current sub was called, and evaluates its operand expression accordingly.** – ikegami Jul 25 '16 at 00:27
  • 1
    @ikegami `return @z` has no comma but still evaluates `@z` in the context of the current sub. – Oktalist Jul 25 '16 at 00:29
  • 1
    @ikegami It's wrong to say that `return` is evaluated after the comma. The comma is evaluated as _part of_ the evaluation of the `return`. – Oktalist Jul 25 '16 at 00:33
  • @Oktalist, That's because the `@z` operator also looks. – ikegami Jul 25 '16 at 00:34
  • 1
    @ikegami In `sub { @z; return 42 }` the `@z` doesn't look. So it's particular to whatever expression is the operand of the `return`. So the correct wording would be **The operand of the `return` dynamically looks...** And I'm not sure how that differs from melpomene's wording in practical terms. – Oktalist Jul 25 '16 at 00:39
  • 1
    @Oktalist, That's a different `@z` operator. [It does look at its context](http://perl5.git.perl.org/perl.git/blob/be2c0c650b028f54e427f2469a59942edfdff8a9:/pp.c#l91), although that `@z` doesn't need to go as far as looking at the sub's context. /// Sure, that wording would work too. That's the general phrasing, whereas mine was specific to the example in question. – ikegami Jul 25 '16 at 00:40
  • @ikegami No, the comma operator only looks at its own context to determine the context of its operands. It's `return` that's the weird one by loooking up the call stack (at runtime). – melpomene Jul 25 '16 at 17:19
  • @ikegami There is no list. `return` supplies scalar context to its operand in this example. And it doesn't matter when `return` is evaluated; this is about context propagation. – melpomene Jul 25 '16 at 19:04
  • @ikegami `,` is not a list constructor. It's either a sequencing operator (in scalar/void context) or a concatenation operator (in list context). There is no list in this case. – melpomene Jul 25 '16 at 19:05
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/118257/discussion-between-melpomene-and-ikegami). – melpomene Jul 25 '16 at 19:08
  • The last expression(s) of a sub gets their context from the caller, not from `return` (which may not even be present). – ikegami Jul 25 '16 at 19:44