Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does scalar sub { (@x, @y) }->() return scalar @y?

Tags:

perl

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?

like image 556
badp Avatar asked Jul 24 '16 08:07

badp


1 Answers

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.

like image 106
melpomene Avatar answered Nov 12 '22 15:11

melpomene