tags:

views:

201

answers:

4

I'd like to perform some convoluted variation of the @a = @b || @c assignment, with the intent of taking @b if non-empty (hence true in a boolean sense), @c otherwise. The documentation explicitely tells me I can't. (And it's right about the fact, too!)

The "||", "//" and "&&" operators return the last value evaluated (unlike C's "||" and "&&", which return 0 or 1).

[...]

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

@a = @b || @c;              # this is wrong
@a = scalar(@b) || @c;      # really meant this
@a = @b ? @b : @c;          # this works fine, though

Unfortunately, it doesn't really tell me why.

What I expected would happen was this:

  • @a = is an array assignment, inducing list context on the right hand side.
  • @b || @c is the right hand side, to be evaluated in list context.
  • || is C-style short-circuit logical or. It evaluates left to right (if needed) and propagates context.
  • @b is evaluated in list context. If true (i.e., non-empty), it is returned.
  • if not, @c is evaluated in list context as well, and returned.

Obviously, my penultimate statement is wrong. Why? And, more importantly, which part of the documentation or sources account for this behavior?

PS: out of the question's scope, the reason I refrain from the documentation's suggestion of using the ternary operator is that my @b is actually a temporary (a function call result).

+6  A: 

The logical-or operator ( "||" ) evaluates it left hand argument in scalar context.

The reason it does this, is to figure out if the argument is true. Boolean context, being a special case of scalar context, forces it into scalar context.


From perldoc perlop "C-style-Logical-Or"

Binary "||" performs a short-circuit logical OR operation. That is, if the left operand is true, the right operand is not even evaluated. ...


From perldoc perldata "Scalar values":

.... The Boolean context is just a special kind of scalar context where no conversion to a string or a number is ever performed.

Brad Gilbert
You've got the answer I was looking for. I found this in perldata: "The Boolean context is just a special kind of scalar context where no conversion to a string or a number is ever performed." Though the reason ("needs to figure out if it is true") does seem a bit dubious to me.
JB
Values can be true or false but aggregates can't. For aggregates "true" usually means "non-empty." When you evaluate an aggregate in scalar context you get a value that is false if the aggregate is empty and true otherwise.
Michael Carman
@Michael: though what you say concords with the explanations all around, it does seem to me both a bit magical (i.e., unexplained) and contradictory to perlsyn's paragraph on truth/falsehood, which mostly says: "empty list is false, anything else is true". The relevant information I needed to accept the fact was brought by Brad: "boolean evaluation coerces to scalar." Non-trivial deduction from perlsyn, IMO.
JB
The key word there is 'is' - a list isn't a boolean value, it's a list value, so saying 'an empty list is false' is really shorthand for 'an empty list, when converted to a boolean, is false'.
ijw
+7  A: 

In perlop, just a few paragraphs before the section you quote:

Binary "||" performs a short-circuit logical OR operation.  That is,
if the left operand is true, the right operand is not even evaluated.
Scalar or list context propagates down to the right operand if it is
evaluated.

This doesn't explicitly state that list context does not propagate to the left operand, but the top of perlop states:

With very few exceptions, these all operate on scalar values
only, not array values.

so we can assume that list context propagating to the right operand is the exception to the rule, and the lack of any statement about the context of the left operand implies the general rule applies.

William Pursell
+2  A: 

It's because || evaluates the left side in scalar context, as does the test argument of ?: . If you can't use the ternary, use a function:

sub or_array (\@\@) {
  return @{$_[0]} if ( scalar @{$_[0]} );
  return @{$_[1]};
}

@a = or_array(@b, @c);
Joe
No doubt there is a cryptic operator in perl6 to take care of this. :)
Ether
A: 

If you can't use the conditional operator directly, you can easily use the slightly-less-terse:

my $ref_b = [ @b ]; # Ideally, just return an arrayref from your function
my @a = @$ref_b ? @$ref_b : @c;

As per above answer, your code does not work since the logical context that the left side of || is evaluated in is a scalar context and thus @b actually becomes scalar(@b) and that is what gets assigned to @a.

DVK