Odd Perl conditional operator behavior - perl

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?

$ 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 =.

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.

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!

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");

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.

Related

Ternary operator doesn't allow iterative operator in it, but if-else does?

I noticed that if I replace the if-else statement I'm using with a ternary operator I end getting a compilation error when I try and run my code. I believe the culprit is the foreach() loop I have inside my if-else. Do you know why the ternary operator isn't behaving the same as the if-else construct in this instance?
My code looks like this
#!/program/perl_v5.6.1/bin/perl5.6.1
use strict;
use warnings;
my $fruits_array_ref = &get_fruits();
if($fruits_array_ref != 0) {
print("$_ is a fruit.\n") foreach(#$fruits_array_ref);
}
else {
print("Maybe you like vegetables?\n");
}
sub get_fruits() {
my #fruit_list;
my $shopping_list = "/home/lr625/grocery_items";
open(my $shopping_list_h, "<", $shopping_list) or die("Couldn't open.\n");
while(my $line = <$shopping_list_h>) {
next if $line =~ /^\#/;
chomp($line);
push(#fruit_list, $line);
}
close($shopping_list_h) or die("Couldn't close.\n");
scalar(#fruit_list) > 0 ? return(\#fruit_list) : return(0);
}
My data in the grocery list looks like
# this is a header
# to my grocery list
apple
banana
grape
orange
I'm replacing the if-else with a ?: operator to look like this now in the main function.
my $fruits_array_ref = &get_fruits();
$fruits_array_ref != 0 ? print("$_ is a fruit.\n") foreach(#$fruits_array_ref) : print("Maybe you like vegetables?\n");
Also, for reference my error says.
syntax error at test.pl line 8, near ") foreach"
Execution of test.pl aborted due to compilation errors.
if-else is a flow control structure, ?-: is an operator that takes expressions as operands. foreach is a flow control structure, not an expression.
You can turn any block of code into an expression by using do:
$fruits_array_ref != 0
? do { print "$_ is a fruit.\n" for #$fruits_array_ref }
: print "Maybe you like vegetables?\n";
But why?
The other answers already pointed out that you can't use the ternary operator the way you tried. For the sake of completeness and to give you some sensible use cases, take a look at the following examples:
#1: Used as a subroutine argument
testSub($var eq 'test' ? 'foo' : 'bar');
Here you can see how the subroutine testSub is called with the argument foo if $var equals the string test. Otherwise testSub will be called with bar. This is useful because you cannot use an if-else structure as a sub argument.
#2: Used for conditional assignment
my $result = $var eq 'test' ? 'foo' : 'bar'; # $result will contain 'foo' or 'bar'
The ternary operator is not meant as a simple replacement to an if-else structure. Since it returns a value (here either foo or bar) it makes sense to also use this value. If you don't intend to use the returned value, you should go for the usual if-else instead.
The foreach statement modifier can only be used at the end of a statement.
Why are you using ?:? You would normally only do that if you wanted a single result.
You could wrap the print...foreach... in a do {...}, or you could use map instead of foreach. Or just leave it as an if/else.
The ternary operator takes arguments before ? and :, see in perlop. It can evaluate an expression and use its result for this. But a loop is not an expression and cannot 'run' inside.
For a demonstration -- you could, if you insisted, call a function which will as a side effect print
sub greet { say "hello" for 1..3 }
my $x = 1;
($x == 1) ? greet() : say "bye";
Actualy doing this in production code is a different matter and would likely be a bad idea. The whole point would be to rely entirely on side effects, opposite to what we normally want to do.
To explain my comment above -- the main point of the ternary operator is to return a value, with a choice between two values, in one statement. While it is "equivalent" to an if-else, its use is (ideally) meant to be very different. So doing some other processing inside the ?: arguments, in any way, is really an abuse of notation, a side-effect, since they are intended to produce a value to be returned. Printing out of it is opposite to the idea of producing and returning a value. This is not a criticism, the operator is used often and by many as a syntactic shortcut.
In this sense I would recommend to revert to an if-else for doing what is shown.

Perl increment or decrement, but not both?

$a++; # ok
$a--; # ok
--$a; # ok
++$a; # ok
--$a++; # syntax error
$a++--; # syntax error
($a++)--; # syntax error
--$a--; # syntax error
On some of these, I can sort of see why- but on like --$a-- there is no ambiguity and no precedence conflict. I'm floored Larry didn't let me do that.. (and don't even get me started on the lack of a floor operator!)
Not that I would need or want to- I was just trying to understand more about how these operators worked and discovered that sort of surprising result..
In the Perldoc for auto increment/decrement we find:
"++" and "--" work as in C.
and slightly earlier on the same page
Perl operators have the following associativity and precedence, listed from highest precedence to lowest. Operators borrowed from C keep the same precedence relationship with each other, even where C's precedence is slightly screwy. (This makes learning Perl easier for C folks.)
Since C returns an rvalue in both cases, Perl does the same. Interestingly, C++ returns a reference to an lvalue for pre-increment/decrement thus having different semantics.
Consider the following:
length($x) = 123;
Just like ++(++$a), there is no ambiguity, there is no precedence conflict, and it would require absolutely no code to function. The limitation is completely artificial[1], which means code was added specifically to forbid it!
So why is length($x) = 123; disallowed? Because disallowing it allows us to catch errors with little or no downside.
length($x) = 123; # XXX Did you mean "length($x) == 123"?
How is it disallowed? Using a concept of lvalues. lvalues are values that are allowed to appear on the left of a scalar assignment.
Some operators are deemed to return lvalues.
$x = 123; # $x returns an lvalue
$#a = 123; # $#a returns an lvalue
substr($s,0,0) = "abc"; # substr returns an lvalue
Some arguments are expected to be an lvalue.
length($x) = 123; # XXX LHS of scalar assignment must be an lvalue
++length($x); # XXX Operand of pre/post-inc/dec must be an lvalue.
The pre/post-increment/decrement operators aren't flagged as returning an lvalue. Operators that except an lvalue will not accept them.
++$a = 123; # XXX Did you mean "++$a == 123"?
This has the side effect of also preventing ++(++$a) which would work fine without the lvalue check.
$ perl -E' ++( ++$a); say $a;'
Can't modify preincrement (++) in preincrement (++) at -e line 1, near ");"
Execution of -e aborted due to compilation errors.
$ perl -E'sub lvalue :lvalue { $_[0] } ++lvalue(++$a); say $a;'
2
Changing ++$a to return an lvalue would allow ++(++$a) to work, but it would also allow ++$a = 123 to work. What's more likely? ++$a = 123 was intentional, or ++$a = 123 is a typo for ++$a == 123?
The following shows that length($x) = 123 would work without the lvalue syntax check.
$ perl -E' say length($x) = 123;'
Can't modify length in scalar assignment at -e line 1, near "123;"
Execution of -e aborted due to compilation errors.
$ perl -E'sub lvalue :lvalue { $_[0] } say lvalue(length($x)) = 123;'
123
The value you see printed is the value of the scalar returned by length after it was changed by the assignment.
For example, what do you expect for:
$a = 1;
$b = --$a++; # imaginary syntax
I think it will be harder to explain that $b is equals to 0 and $a is 1, isn't it?... In any case, I donĀ“t remember any real example where that syntax would be useful. It's useless and ugly

Meaning of // operator in perl

I am new to perl. Can anyone please explain the meaning of // operator in perl.
It's the definedness operator. The expression:
A // B
will return A if it's defined, otherwise B.
It's very useful for getting default values if the source of the information is not defined, with things like:
$actualBalance = $balanceFromBank // 0;
or:
$confirmation = $userInput // "N";
See the relevant part of the perlop page for more detail and make a link of perlop for future reference, since Google searches and the punctuational Perl code don't mix that well :-)
The // operator is a logical defined-or. Perlop says:
Although it has no direct equivalent in C, Perl's // operator is
related to its C-style or. In fact, it's exactly the same as ||,
except that it tests the left hand side's definedness instead of its
truth. Thus, 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).
Usually, this is the same result as defined(EXPR1) ? EXPR1 : EXPR2
(except that the ternary-operator form can be used as a lvalue, while
EXPR1 // EXPR2 cannot). This is very useful for providing default
values for variables. If you actually want to test if at least one of
$a and $b is defined, use defined($a // $b) .
In short: It returns the left side if that expression is defined (as in not undef), or the right side.
my $foo = undef;
say $foo // 42;
# 42
my $bar = 'bar';
say $bar // 42;
# bar
It's so called defined-or operator, which has been implemented in Perl 5.10. Example from the doc:
The following expression:
$a // $b
... is merely equivalent to
defined $a ? $a : $b
And the statement:
$c //= $d;
... can now be used instead of
$c = $d unless defined $c;
Here's how || and // are different:
use 5.010;
my $rabbits = 0;
say $rabbits || 1; # 1, as 0 || 1 evaluates to 1
say $rabbits // 1; # 0, as 0 is not `undef`
That is "defined-or". $abc // "default" is equivalent to defined($abc) ? $abc : "default". Meaning if the left side of // has a defined value then that value is used, otherwise the right side of it.
See "Logical defined-or" in the perlop man page.
is defined or
like,
my $a //= 3;
will assign 3 to $a
it is different from ||, which is simply, or in that:
my $a = "";
$a //= 3;
print "|$a|\n";
$a = "";
$a ||=5;
print "|$a|\n";
will print only |5|, because in the first case $a is defined (with a false value), while in the second it matters if $a evaluates to true or not.

Perl ternary errantly enters "else" clause?

I have the following code:
# List of tests
my $tests = [("system_test_builtins_sin", "system_test_builtins_cos", "system_test_builtins_tan")];
# Provide overrides for certain variables that may be needed because of special cases
# For example, cos must be executed 100 times and sin only 5 times.
my %testOverrides = (
system_test_builtins_sin => {
reps => 5,
},
system_test_builtins_cos => {
reps => 100,
},
);
my %testDefaults = (
system_test_reps => 10,
);
# Execute a system tests
foreach my $testName (#$tests)
{
print "Executing $testName\n";
my $reps;
if (exists $testOverrides{$testName}{reps})
{ $reps = $testOverrides{$testName}{reps}; }
else
{ $reps = $testDefaults{system_test_reps}; }
print "After long if: $reps\n";
exists $testOverrides{$testName}{reps} ? $reps = $testOverrides{$testName}{reps} : $reps = $testDefaults{system_test_reps};
print "After first ternary: $reps\n";
exists $testOverrides{$testName}{reps} ? $reps = $testOverrides{$testName}{reps} : print "Override not found.\n";
print "After second ternary: $reps\n";
}
This gives the following output:
Executing system_test_builtins_sin
After long if: 5
After first ternary: 10
After second ternary: 5
Executing system_test_builtins_cos
After long if: 100
After first ternary: 10
After second ternary: 100
Executing system_test_builtins_tan
After long if: 10
After first ternary: 10
Override not found.
After second ternary: 10
This output is most unexpected! I don't understand why the first ternary seems to always be executing the "if false" clause. It is always assigning a value of 10. I also tried changing the "false" clause to $reps = 6, and I saw that it always got the value of 6. Why does the ternary's logic depend on the content of the third (if false) clause?
Here's a simpler script that illustrates the problem:
#!/usr/bin/perl
use strict;
use warnings;
my $x;
1 ? $x = 1 : $x = 0;
print "Without parentheses, \$x = $x\n";
1 ? ($x = 1) : ($x = 0);
print "With parentheses, \$x = $x\n";
It produces this output:
Without parentheses, $x = 0
With parentheses, $x = 1
I'm not sure that the relationship between assignment and ?: can be complete explained by operator precedence. (For example, I believe C and C++ can behave differently in some cases.)
Run perldoc perlop and search for "Conditional Operator", or look here; it covers this exact issue (more concisely than I did here).
In any case, I think that using an if/else statement would be clearer than using the ?: operator. Or, since both the "true" and "false" branches assign to the same variable, a better use of ?: would be to change this:
exists $testOverrides{$testName}{reps}
? $reps = $testOverrides{$testName}{reps}
: $reps = $testDefaults{system_test_reps};
to this:
$reps = ( exists $testOverrides{$testName}{reps}
? testOverrides{$testName}{reps}
: $testDefaults{system_test_reps} );
But again, the fact that I had to wrap the line to avoid scrolling is a good indication that an if/else would be clearer.
You might also consider using the // operator, unless you're stuck with an ancient version of Perl that doesn't support it. (It was introduced by Perl 5.10.) It's also known as the "defined-or" operator. This:
$x // $y
is equivalent to
defined($x) ? $x : $y
So you could write:
$reps = $testOverrides{$testName}{reps} // $testDefaults{system_test_reps};
This doesn't have exactly the same semantics, since it tests the expression using defined rather than exists; it will behave differently if $testOverrides{$testName}{reps} exists but has the value undef.
The -p option to B::Deparse is illuminative for problems like this:
$ perl -MO=Deparse,-p -e '$condition ? $x = $value1 : $x = $value2'
(($condition ? ($x = $value1) : $x) = $value2);
As Keith Thompson points out, it is all about the precedence. If the condition is false, the ultimate assignment is $x = $value2. If the condition is true, then the assignment is ($x = $value1) = $value2 -- either way the outcome is to assign $value2 to $x.
I would do it this way (I dont mind using ternary operators)
$reps = exists($testOverrides{$testName}{reps}) ?
$testOverrides{$testName}{reps} :
$testDefaults{system_test_reps};
HTH
Thanks for giving us a sample of your code. Now we can tear it to pieces.
Don't use the ternary operator. It's an infection left over to originally make C programmers feel comfortable. In C, the ternary operator was used because it was originally more efficient than an if/else statement. However, compilers are pretty good about optimizing code, so that's no longer true and now it's discouraged in C and C++ programming. Programming in C is hard enough as it is without ternary operators mucking about.
The Perl compiler is also extremely efficient at optimizing your code, so you should always write for maximum clarity, so others who aren't as good as programming and get stuck maintaining your code can muddle through their job.
The problem you're having is one of operator precedence. You're assuming this:
(exists $testOverrides{$testName}{reps})
? ($reps = $testOverrides{$testName}{reps})
: ($reps = $testDefaults{system_test_reps});
I would too. After all, that's what I pretty much mean. However, the assignment operator has lower precedence than the ternary operator. What's really happening is this:
(exists $testOverrides{$testName}{reps})
? ($reps = $testOverrides{$testName}{reps}) : ($reps))
= $testDefaults{system_test_reps});
so, the final assignment is always happening to $reps.
It's much better if you use if/else:
if (exists $testOverrides{$testName}{reps}) {
$reps = = $testOverrides{$testName}{reps};
}
else {
$reps = $testDefaults{system_test_reps};
}
No precedence issues, easier to read, and just as efficient.

Assignment inside Perl ternary conditional operator problems

This snippet of Perl code in my program is giving the wrong result.
$condition ? $a = 2 : $a = 3 ;
print $a;
No matter what the value of $condition is, the output is always 3, how come?
This is explained in the Perl documentation.
Because of Perl operator precedence the statement is being parsed as
($condition ? $a= 2 : $a ) = 3 ;
Because the ?: operator produces an assignable result, 3 is assigned to the result of the condition.
When $condition is true this means ($a=2)=3 giving $a=3
When $condition is false this means ($a)=3 giving $a=3
The correct way to write this is
$a = ( $condition ? 2 : 3 );
print $a;
We got bitten by this at work, so I am posting here hoping others will find it useful.
Once you have an inkling that you might be suffering from precedence problems, a trick to figure out what Perl thought you meant:
perl -MO=Deparse,-p -e '$condition ? $a= 2 : $a= 3 ; print $a;'
In your case, that'll show you:
(($condition ? ($a = 2) : $a) = 3);
print($a);
-e syntax OK
...at which point you should be saying "oh, that explains it"!
Just to extend the previous answer... If, for whatever reason, the assignments need to be part of the conditional, you'd want to write it thusly:
$condition ? ($a=2) : ($a=3);
This would be useful if you're assigning to different variables based on the condition.
$condition ? ($a=2) : ($b=3);
And if you're choosing the variable, but assigning the same thing no matter what, you could even do this:
($condition ? $a : $b) = 3;
Because of Perl operator precedence the statement is being parsed as:
($condition ? $a = 2 : $a ) = 3 ;
Because the ?: operator produces an assignable result, 3 is assigned to the result of the condition.
When $condition is true this means $a=2=3 giving $a=3
When $condition is false this means $a=3 giving $a=3
The correct way to write this is
$a = $condition ? 2 : 3;
In general, you should really get out of the habit of using conditionals to do assignment, as in the original example -- it's the sort of thing that leads to Perl getting a reputation for being write-only.
A good rule of thumb is that conditionals are only for simple values, never expressions with side effects. When you or someone else needs to read this code eight months from now, would you prefer it to read like this?
$x < 3 ? foo($x) : bar($y);
Or like this?
if ($x < 3) {
$foo($x);
} else {
$bar($y);
}
One suggestion to Tithonium's answer above:
If you are want to assign different values to the same variable, this might be better (the copy-book way):
$a = ($condition) ? 2 : 3;