Home > Software design >  Perl eval or do return scalar?
Perl eval or do return scalar?

Time:07-23

I'm new to perl... could someone help me understand why perl eval behave like this:

eval with array or list: pass

eval with list or do: pass

eval with array or do: return value is not the expected result, it's taking the last element.

80> eval{(1,2,3)} or do {}
Useless use of a constant (2) in void context at reply input line 1.
$res[34] = 3

81> eval{[1,2,3]} or do {}
$res[35] = [
  1,
  2,
  3
]

82> eval{(1,2,3)}
$res[36] = [
  1,
  2,
  3
]

83> eval{[1,2,3]}
$res[37] = [
  1,
  2,
  3
]

CodePudding user response:

You need to start by studying Perl's concept of context.

The short version is that operators are evaluated in list, scalar or void context. The context of an operator is determined by the operator of which it is an operand.

In list context, an operator may return any number (0 or more) scalars.

In scalar context, an operator must return exactly one scalar (no more, no less).

In void context, the scalars returned by an operator are discarded.

Operators can and do and return different values depending on the context. Any operator that nominally returns multiple scalars must necessarily return something different in list context and scalar context.


Now that you've studied Perl's concept of context, I'll cover how it applies to the list/comma operator.

To do that, we'll look at these two statements.

my @a = (1,2,3);
my $s = (1,2,3);

Note that all ( ) does is change precedence like ( ) in mathematics. It doesn't result in any code. It doesn't create any kind of data structure.

In the first case, we have a list assignment operator. It evaluates its right-hand side operand in list context. This means that 1,2,3 is evaluated in list context.

In list context, 1,2,3 evaluates each item of the list in list context, and returns every scalar produced. As such, it returns 1, 2 and 3.

In the second case, we have a scalar assignment operator. It evaluates its left-hand side operand in scalar context. This means that 1,2,3 is evaluated in scalar context.

In scalar context, 1,2,3 evaluates each item but the last in void context, then evaluates the last item in scalar context, and returns the scalar produced by the last time. As such, it's short for do { 1;2;3 }, and it returns 3.


Finally, let's examine the context of each operator in the expressions you provided.

The perl debugger evaluates provided code in list context.

or imposes a scalar context on its left-hand side operand.

[ ] evaluates its initializaing expression in list context.

eval propagates its context to the last statement of the block.

eval{(1,2,3)} or do {}

# Order   Context   Operator        Produces
# -----   -------   -------------   --------
# 6       list      or              3
# 5       scalar       eval         3
# 4       scalar          ,         3
# 1       void               1      <nothing>
# 2       void               2      <nothing>
# 3       scalar             3      3
# -       list         do           <not evaluated>
eval{[1,2,3]} or do {}

# Order   Context   Operator        Produces
# -----   -------   -------------   --------
# 7       list      or              ref to array
# 6       scalar       eval         ref to array
# 5       scalar          []        ref to array
# 4       list               ,      1 and 2 and 3
# 1       list                  1   1      
# 2       list                  2   2
# 3       list                  3   3
# -       list         do           <not evaluated>
eval{(1,2,3)}

# Order   Context   Operator        Produces
# -----   -------   -------------   --------
# 5       list      eval            1 and 2 and 3
# 4       list         ,            1 and 2 and 3
# 1       list            1         1      
# 2       list            2         2
# 3       list            3         3
eval{[1,2,3]}

# Order   Context   Operator        Produces
# -----   -------   -------------   --------
# 6       list      eval            ref to array
# 5       list         []           ref to array
# 4       list            ,         1 and 2 and 3
# 1       list               1      1      
# 2       list               2      2
# 3       list               3      3

CodePudding user response:

The code snippets in the question are run in a perl debugger, so in list context.

Let's start from claryfing the context aspect of this, the crucial concept in Perl. From eval

In both forms, the value returned is the value of the last expression evaluated inside the mini-program; a return statement may also be used, just as with subroutines. The expression providing the return value is evaluated in void, scalar, or list context, depending on the context of the eval itself.

So if the return of eval is assigned to a scalar then the (last) expression in the block is evaluated in the scalar context. If the eval is or-ed (with the return of the doblock for example, like here), it is scalar context again.

In such a case the comma operator behaves as

In scalar context it evaluates its left argument, throws that value away, then evaluates its right argument and returns that value.

While comma is evaluating and throwing values away it acts in void context (those values aren't used for anything), and those are the warnings we see:

perl -wE'$val = eval { (7, 8, 9) }'

prints

Useless use of a constant (7) in void context at -e line 1.
Useless use of a constant (8) in void context at -e line 1.

since the first two values are discarded while the last one does get used. Same for

perl -wE'eval { (7, 8, 9) } or do { 1 }'

and for the same reason, as 9 gets or-ed with the return value of do.

If nothing at all were to be done with this

perl -wE'eval { (7, 8, 9) }'

then we would get

Useless use of a constant (7) in void context at -e line 1.
Useless use of a constant (8) in void context at -e line 1.
Useless use of a constant (9) in void context at -e line 1.

So these warnings are from the comma operator, while the context for its behavior is supplied by the context the eval is in.

Now, if the eval's return is used in a list context, like passed to a map or assigned to an array, then the list context inside the block makes the comma do

In list context, it's just the list argument separator, and inserts both its arguments into the list.

So that list (7, 8, 9) gets orderly returned and there are no warnings.

There is one more case to clarify, with [7, 8, 9] -- now the [ ] are the constructor for an anonymous array so the comma operator inside is in the list context and "inserts" its arguments into the list, which is used to build an array ref. No reason for warnings from the comma.


The fun part: when 1 is an element of the list there is no warning about it being a useless constant etc! Like, for

perl -wE'eval { (7, 1, 9) }'

we get

Useless use of a constant (7) in void context at -e line 1.
Useless use of a constant (9) in void context at -e line 1.

The reason being that 1 is (apparently) special-cased this way so that one can do things like

1 while ...

Thanks for Dave Mitchell for letting me know about why 1 is excluded from warnings.

Thanks to ikegami for informing about debugger and its list context.

  •  Tags:  
  • perl
  • Related