Assignment inside Perl ternary conditional operator problems - perl

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;

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.

'=' is working in place of 'eq'

Hi I am writing a perl script to accomplish some task.In my script I am using one if loop to compare two strings as shown below.
if($feed_type eq "SE"){
...........}
The above code is not giving me any warning but the output is not as I expected.
Instead of 'eq' if I use '=' I am getting a warning saying expectng '==' but '=' is present. But I am getting the expected output.
Ideally for string comparison I must use 'eq' and for numbers '=='. In this case it's not working. Can anyone figure out what is the problem here?
More info:
This if loop is present in a subroutine. $feed_type is an input for this subroutine. I am reading the input as below:
my $feed_type=#_;
The problem is fixed. I just changed the assignemet statement of feed_type as below
my $feed_type=$_[0];
and it's reading the value as SE and the code is working.
but I still dont know why my $feed_type=$_[0]; didn't work.
= might well work in place of eq, but not for the reason you think.
#!/usr/bin/env perl
use strict;
use warnings;
my $test = "fish";
my $compare = "carrot";
if ( $test = $compare ) {
print "It worked\n";
}
Of course, the problem is - it'll always work, because you're testing the result of an assignment operation.*
* OK, sometimes assignment operations don't work - this is why some coding styles suggest testing if ( 2 == $result ) rather than the other way around.
This is about a core Perl concept: Context. Operators and functions work differently depending on context. In this case:
my $feed_type = #_;
You are assigning an array in scalar context to the variable. An array in scalar context returns its size, not the elements in it. For this assignment to work as you expect, you have to either directly access the scalar value you want, like you have suggested:
my $feed_type = $_[0];
...or you can put your variable in list context by adding parentheses:
my ($feed_type) = #_;
This has the benefit of allowing you to perform complex assignments, like this:
my ($first, $second, #rest) = #_;
So, in short, the problem was that your comparison that looked like this:
if($feed_type eq "SE")
Was actually doing this:
if(1 eq "SE")
And returning false. Which is true. Consider this self-documenting code:
sub foo {
my $size = #_;
if ($size == 1) {
warn "You passed 1 argument to 'foo'\n";
return;
}
}
Which demonstrates the functionality you inadvertently used.
= is used to assign the variable a value, so you would need '==' to compare numerical values and 'eq' for strings.
If it's complaining about not using '==', then it's because $feed_type is not a string.
I can't tell as there's no more code. Whatever $feed_type is set by you need to confirm it actually contains a string or if you're even referencing it correctly.

Reversing the Operand of Smart Matching operator doesn't give same result

I am facing a strange problem here in the working of Smart Matching Operator..
I have read that the order of operand while using the Smart Matching Operator(~~) doesn't matter, and it gives the same result.. But in an example which I have shown below, this doesn't work..
I want to check whether an element is amongst one of the elements of a given array or not..
Below are the two ways I tried : -
First way: - ($result ~~ #nums)
#!/perl/bin
use v5.14;
my #nums = qw( 1 2 3 27 42 );
my $result = 27;
say "The result [$result] is one of the input values (#nums)" if $result ~~ #nums;
Second way: - (#nums ~~ $result)
#!/perl/bin
use v5.14;
my #nums = qw( 1 2 3 27 42 );
my $result = 27;
say "The result [$result] is one of the input values (#nums)" \
if #nums ~~ $result;
However, the first way is working fine, and it is printing the statement, but in the second way, it is not printing..
i.e. : - #nums ~~ $result is not giving the same result as $result ~~ #nums
I can't understand why this is happening..
Can anyone help me. I am unable to find this problem on SO.
according to to the perlop, it says (among many other stuff):
It is often best read aloud as "in", "inside of", or "is contained
in", because the left operand is often looked for inside the right
operand.
you may want to look at the table there, especially on the following sections:
Left Right Description and pseudocode
===============================================================
Any ARRAY smartmatch each ARRAY element[3]
like: grep { Any ~~ $_ } ARRAY
Any Num numeric equality
like: Any == Num
and remember that if Any is array and Num is a scalar, the following are equal:
Any == Num <=> scalar(Any) == Num

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.

Odd Perl conditional operator behavior

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.