Home > Enterprise >  Perl defined-or in a list context, why a scalar?
Perl defined-or in a list context, why a scalar?

Time:11-16

use strict;
use warnings;
use Test::More;

subtest 'explicit array' => sub  {
    my @row = (1,2,3);
    # let's disassamble the array.
    # without default it works:
    my ($one, $two, $three) =  @row;
    is($one, 1, 'one');
    # this works too:
    ($one, $two, $three) =  @row ? @row : (10,20,30);
    is($one, 1, 'one');
    # and the default hits
    my @emptyness;
    ($one, $two, $three) = @emptyness ? @emptyness : (10,20,30);
    is($one, 10, 'one default');
    # however, this squashes the array to a scalar
    ($one, $two, $three) =  @row // (10,20,30);
    is($one, 3, 'scalar, length');
    is($two, undef, 'nothing else');
    # shouldn't 'defined-or' be equivalent to a ternary with a check against undef?
    # ($one, $two, $three) = defined @emptyness ? @emptyness : (10,20,30); # fails!
    # "Can't use 'defined(@array)' (Maybe you should just omit the defined()?)"
    # Probably @array // ... should fail in the same way, but instead it returns @array
    # in a scalar context.
    # so maybe this is a bug
};


done_testing();

Or can anybody give me a reasonable explanation for this behavior?

CodePudding user response:

The behavior you are observing is the intended one. This is documented in perlop, in the section Logical Defined-Or:

EXPR1 // EXPR2 returns the value of EXPR1 if it's defined, otherwise, the value of EXPR2 is returned. (EXPR1 is evaluated in scalar context, EXPR2 in the context of // itself).

And, perldoc later provides the following example:

In particular, this means that you shouldn't use this for selecting between two aggregates for assignment:

@a = @b || @c;            # This doesn't do the right thing
@a = scalar(@b) || @c;    # because it really means this.
@a = @b ? @b : @c;        # This works fine, though.

CodePudding user response:

defined(@array) is a bit of a weird construct and Perl is trying to get rid of it. From perldoc -f defined:

Use of "defined" on aggregates (hashes and arrays) is no longer supported. It used to report whether memory for that aggregate had ever been allocated.

That's not testing if it currently has any elements! To check for size, simply use the array variable in scalar context (such as a conditional):

if( @array ) { ... }

And, Dada has already explained what the defined-or operator does, which adds its own twist to it.

CodePudding user response:

// must evaluate its left-hand side operator in scalar context since it requires a scalar to evaluate for defined-ness.

@a returns the number of elements the array contains in scalar context.

So @a // ... always returns the number of elements in @a (since all numbers are defined values).


The concepts of defined-ness or truth-ness aren't defined for a sequence of scalars ("list") as a whole. They only apply to individual scalars. As such, //, &&, and, ||, or, !, not and ?: require a scalar from their left-most operand, so they evaluate it in scalar context. xor needs to test the truth of both of its operands, and thus evaluates both in scalar context.

$ perl -M5.010 -e'
   sub cx { print wantarray ? " list  " : " scalar"; $_[0] }

   print "// ";  @a =   cx($u) //  cx();     say "";
   print "&& ";  @a =   cx(0)  &&  cx();     say "";
   print "and";  @a = ( cx(1)  and cx() );   say "";
   print "|| ";  @a =   cx(0)  ||  cx();     say "";
   print "or ";  @a = ( cx(0)  or  cx() );   say "";
   print "xor";  @a = ( cx(0)  xor cx(0) );  say "";
   print "!  ";  @a =   !   cx();            say "";
   print "not";  @a =   not cx();            say "";
   print "?: ";  @a =  cx() ? 0    : 0;
                 @a =  1    ? cx() : 0;
                 @a =  0    ? 0    : cx();   say "";
'
//  scalar list
&&  scalar
and scalar list
||  scalar list
or  scalar list
xor scalar scalar
!   scalar
not scalar
?:  scalar list   list

You might be under the incorrect impression that @a always returns a list of its elements, and that this gets coerced into a count when evaluated in scalar context. But that's not the case.

There's simply no such thing as a list. When we say "evaluates to a list" or "returns a list", we simply mean "adds zero or more scalar to the stack". There's absolutely no difference between return a scalar and returning "a list of one scalar". Both refer to adding a scalar to the stack. Since only scalars are added to the stack, there's nothing that can be cor

Since there's no list data structure being returned, there's nothing that can be magically coerced to a scalar in scalar context; its up to every operator to return a single scalar when evaluated in scalar context. This permits each operator to choose what they want to return in scalar context. It varies greatly.

In short, @a returns the number of elements in scalar context, not the elements it contains like it does in list context.

          -------------------------- (Scalar context) Returns 3
         |             ------------- (List context) Returns three scalars
         |            |
      vvvv     vvvvvvvv
() =  @row // (10,20,30);
          -------------------------- (Scalar context) Returns 3
         |       ------------------- (List context) Returns three scalars
         |      |            ------- (List context) Returns three scalars
         |      |           |
      vvvv   vvvv    vvvvvvvv
() =  @row ? @row : (10,20,30);

Finally, let's analyze @row // ....

We've already established that @row is evaluated in scalar context in the above, and that it returns the number of elements in the array in array when it does.

Well, numerical values are necessarily not undef, so they are all defined. So that means that the right-hand size of @row // ... will never get evaluated. You might as well have written scalar(@row).

  •  Tags:  
  • perl
  • Related