Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

perl

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?

like image 264
Jan Avatar asked Nov 15 '21 07:11

Jan


People also ask

What is scalar context in Perl?

In a list context, Perl gives the list of elements. But in a scalar context, it returns the number of elements in the array. When an operator functions on Scalars then its termed as Scalar Context.

What is scalar function in Perl?

A scalar is a variable that stores a single unit of data at a time. The data that will be stored by the scalar variable can be of the different type like string, character, floating point, a large group of strings or it can be a webpage and so on.

Is a hash a scalar in Perl?

Hash in Perl is a set of key/value pairs. Perl provides us the flexibility to assign the hash to a List type and a Scalar type, known as LIST Context and SCALAR Context respectively. The assignment of a hash to a list type in Perl is accomplished by making a list with the keys and values as its elements.

What is scalar array in Perl?

An array is an ordered and mutable list of scalars. In Perl, scalars are defined as a single unit of data such as an integer, a floating-point, a character, a string, and so on. They are preceded by a $ when defined.


Video Answer


3 Answers

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.
like image 80
Dada Avatar answered Oct 19 '22 23:10

Dada


// 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 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).

like image 31
ikegami Avatar answered Oct 19 '22 22:10

ikegami


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.

like image 20
brian d foy Avatar answered Oct 19 '22 22:10

brian d foy