views:

229

answers:

5

I'm doing some work in Perl and I ran across an odd result using the conditional operator.

The code in question:

($foo eq "blah") ? @x = @somearray : @y = ("another","array");

Trying to compile this code results in the error "Assignment to both a list and a scalar at XXX line YY, near ');'". In trying to pinpoint the source of the error I've written this using a couple different ways of representing an array in Perl and they all return with the same error. Now at first I thought it was just some dumb obvious mistake with the assignment statements, but just to satisfy my curiosity I rewrote the statement in a more verbose way:

if($foo eq "blah") {
    @x = @somearray;
} else {
    @y = ("another","array");
}

That version of the code compiled perfectly fine.

Is there some fine distinction between how the conditional operator works and a basic if-else statement works that I'm missing here? I always understood the conditional operator to be just a short-hand version of the second statement. If there isn't a functional difference between the two, why would Perl object to the first statement, but not the second?

+15  A: 
$ perl -MO=Deparse -e'($foo eq "blah") ? @x = @somearray : @y = ("another","array");'
Assignment to both a list and a scalar at -e line 1, near ");"
-e had compilation errors.
$foo eq 'blah' ? (@x = @somearray) : @y = ('another', 'array');
$ perl -MO=Deparse -e'($foo eq "blah") ? @x = @somearray : (@y = ("another","array"));'
$foo eq 'blah' ? (@x = @somearray) : (@y = ('another', 'array'));
-e syntax OK

Note the parentheses: ?: binds tighter than =.

ephemient
Thanks, worked like a charm!
warhorus
While this is why Perl doesn't like the code I'd argue that the real root of the error is abuse of the `?:` operator.
Michael Carman
+7  A: 

The perlop documentation clearly states you should put parentheses around assignment operators.

Failing to use parentheses is a rod for your own back if you don't understand operator precedence. Stop trying to be too smart for your own good!

PP
"trying to be too smart for your own good" -- that's a good definition of a programmer :)
Ether
I've had heated arguments with "programmers" who rubbish the idea of using parentheses. It's fine if you keep a table of operator precedences in your head and don't switch languages regularly. I've been programming for more than 19 years and I switch languages often enough to know not to make assumptions about the underlying language. Plus it means I can port expressions with less risks.
PP
I have deliberately refrained from memorizing the C and C++ precedence tables. I figure I'll do less harm if I'm uncertain where the parens go.
David Thornley
It costs nothing to parentheticate, even in cases where precedence means it's redundant. My rule of thumb is: if you (or the coder who comes after you) will have to think about precedence when looking at your code, then play it safe and make it explicit. It also proves you've considered the issue.
RET
+9  A: 

The Perl conditional operator is meant to be

$variable = (expression) ? true assignment : false assignment;

What you're doing looks like it should work and is basically the same as the if/else statement. But is just different enough from the norm to have issues.

Kenny Drobnack
In C and Perl it is perfectly valid to ignore the result of an expression.
PP
@PP Yeah, but not to the poor sap reading it.
Schwern
+2  A: 

This would be a good spot to use the lower precedence 'and' and 'or' operators.

$foo eq 'blah' and @x = @somearray or @y = ('another', 'array');

provided you are sure that @x = @somearray will always be true. or you could flip them around.

Eric Strom
ephemient
In the name of excessive trickery, `$foo eq 'blah' and (undef, @x) = (undef, @somearray) or @y = ('another', 'array');` will work even if `@somearray == 0` :D
ephemient
I disagree, this would be a good use for `if(){} else {}`. Anything else is hard to read and excessively clever. The evident confusion in the other comments provides independent verification of the difficulty inherent in the overuse and abuse of the short circuiting `and` operator.
daotoad
[previous comment deleted for being just wrong]
mobrule
+6  A: 

This is somewhat orthogonal to your question, but it bears pointing out: Perl's conditional operator propagates context from the first argument down into the second or third arguments, so this would give you undesired results:

$x = ($foo eq "blah") ? $somevalue : ("another","array");

If the conditional were false, $x would instead be assigned a single integer value 2 (the number of elements in the third argument).

If on the other hand you were attempting to perform a purely scalar assignment:

# this is wrong, for the same order-of-operations reasons as with arrays
($foo eq "blah") ? $x = $somevalue : $x = "another value";

This would be a reasonable (and the best) way to resolve the situation:

$x = ($foo eq "blah") ? $somevalue : "another value";

Likewise, you could optimize your original code this way:

@x = ($foo eq "blah") ? @somearray : ("another","array");
Ether
Orthogonal or not, it's a great point to keep in mind while dealing with the ?: operator!
warhorus
...except that the original code is being absurdly tricky and assigning to `@x` in one case and `@y` in the other case.
ephemient
`($foo eq "blah") ? @x : @y = ($foo eq "blah") ? @somearray : qa(another array);` will behave like the original code.
ephemient
@ephemient: yeah I totally didn't catch that; my eye skipped past the `@y` entirely :) Case in point that the longer if/else form would be more readable in this situation.
Ether
For what it's worth, I threw that example together on the fly when a few other heads around the office couldn't figure it out either. In the original statement, that @y would have been a @x. Didn't think it warranted editing since the question was about how the ?: operator worked.
warhorus