How does elsif work in Perl? - perl

Why does Perl have 'elsif' and not 'elseif'?
I am doing something like this:
$somevariable = 0;
if ($idcount==5)
{
# Do something
if (somestatement is true) # 1
{
$somevariable = 1;
}
}
elsif ($idcount > 1 && somevariable = 0)
{
# Do something else here
}
The code never comes to the elsif statement. In this case idcount is actually 5, so it comes to the first if statement. But an inner if statement (1) is not true, so $somevariable is still 0. Since idcount is 5, it should come to elsif as well since $idcount is greater than 1 and $somevariable is still 0.
Maybe I am just tired and not seeing something obvious.

No, anything in an elsif only has a chance to be executed if none of the previous if/elsif conditions have been true, that's the "else" part of it.
If you want both to be evaluated, just make it an if instead of an elsif.

elsif ($idcount > 1 && somevariable = 0)
Should be:
elsif ($idcount > 1 && $somevariable == 0)
The assignment operator returns the assigned value, 0, which equates to false, thus the logical and '&&' always fails.

Here is how the Llama book answers the elsif vs elseif question:
"Because Larry says so."

In any if-elsif-else construct, you only ever execute the first branch whose condition is true. Once that branch executes, the program picks up after the entire if-elsif-else.
If you want to do something with possible multiple branches, you can construct your own case-like syntax with multiple if blocks. There is a given-when construct from Perl 5.10 but its has retrograded into experimental status and may be removed from the language.
As for the spelling of elsif, there are a couple of factors involved.
In C, you write
if( ... ) {
... }
else if ( ... ) {
That sometimes causes a "dangling else" problem. If you had started but didn't finish inserting an if-else if right before another, but distinct, if, you've combined the two through the dangling else. Larry Wall didn't like that, so he combined the 'else if" into one word, "elseif". However, that looked odd to Larry since the rule is "i before e except after c, ...", so he fixed that by taking out the "e" to make it just "elsif".

I don't see what your confusion has to do with the fact that perl uses "elsif" rather than "else if" or "elseif". In every language I've ever used, from pre-1970s-style FORTRAN to Java and including along the way such "highlights" as Easytreive, if you do "if cond ... else ..." the stuff after the else doesn't get executed if the original "cond" is true. That's what "else" means.
In your case, the "cond" is "($idcount==5)", which you already stated is true, and the else part that doesn't get executed because it's true is "if ($idcount > 1 && $somevariable == 0) ...".

Perl has elsif because perl doesn't allow C style "else if" without brackets. Instead, you would need to have something like:
if (x) {
}
else {
if(y) {
}
}

"elsif" in Perl works exactly the same as "elseif", "else if", and "elif" in other languages. It's just a matter of spelling.

Here is the equivalent of what was posted in the question.
use strict;
use warnings;
$somevariable = 0;
if ($idcount==5)
{
# do something
if ( 1 )
{
$somevariable = 1;
}
}
else
{
if ($idcount > 1 && somevariable == 0) # this line was wrong in the question
{
# do something else here
}
}
or
use Modern::Perl;
$somevariable = 0;
given($idcount){
when( 5 ){
# do something
if ( 1 ){
$somevariable = 1;
}
}
when( $_ > 1 && $somevariable == 0 ){
# do something else here
}
}
use Modern::Perl;
is the same as
use strict;
use warnings;
use 5.010;

If you're going to use if...elsif in any language, your conditions should be mutually exclusive. The two conditions in your example could both be true simultaneously, so you should break that up into two separate if statements.

Related

Perl “do { … } if …” as expression

my $x = do { 3; } if 1; say $x # works
my $x = (do { 3; } if 1); say $x # syntax error
How come? If the do block is an expression, why can't it be parenthesised? If it's not, how does the first one parse?
A compound statement used for flow control (if BLOCK), as well as one with the statement modifier (used here, the postfix if), cannot appear inside parenthesis.
This restriction makes sense since such a statement may or may not return a value
if executes the statement once if and only if the condition is true.
(original emphasis)
A side note. The first example runs without warnings but it has undefined behavior, what must be avoided. From the end of the section Statement Modifiers in perlsyn
NOTE: The behaviour of a my, state, or our modified with a statement modifier conditional or loop construct (for example, my $x if ...) is undefined. The value of the my variable may be undef, any previously assigned value, or possibly anything else. Don't rely on it. Future versions of perl might do something different from the version of perl you try it out on. Here be dragons.
(original emphasis)
Any instances of this should be rewritten, and Perl::Critic has a policy for it, making it easier to find them
It's not that the do that's the problem, it's the postfix if. That postfix can't appear inside the parens:
$ perl -E 'my $x = ( 1 if 1); say $x'
syntax error at -e line 1, near "1 if"
Execution of -e aborted due to compilation errors.
Instead, you can use the conditional operator ?: with a do in one of the branches:
$ perl -E 'my $x = ( time % 2 ? do { 1 } : () ); say $x'
my $x = do { 3; } if 1;
is actually equivalent to
( my $x = do { 3; } ) if 1;
Note that you shouldn't execute my conditionally. (More precisely, you shouldn't use a my variable that hasn't been executed. Your code is technically ok since the my is always executed before $x is used.)
An expression (my $x = do { ... }) modified by a statement modifier (if 1) is a statement.
The inside of parens must be an expression, not a statement.
You can't do
( $x = 3; )
( sub f { } )
( if (f()) { g() } )
( g() if f(); )
( g() if f() )
You get the idea.
if 1 is a statement modifier. my $x = do { 3; } is a statement; do { 3; } is an expression.

Trying to write a simple loop

Could someone help me with a loop please. I'm meant to be writing a program which simply asks you to guess a number between 1 and 10. If it's not the correct answer you get another chance, etc.
I can get my script to print correct/not correct one time, but how do I add into this script a possibility for the user to try again (until they guess the right number)?
Here's my basic script, which I'm sure is very simplistic and probably full of errors. Could someone help me sort out this simple problem?
Sorry for the bad layout, but I don't understand how to place my script on this site, sorry!
use strict;
use warnings;
print "Hello, I've thought of a number, do you know what number it is?\n";
sleep (1);
print "Try and guess, type in a number between 1 and 10!\n";
my $div = <STDIN>;
my $i = 0;
my $int = int(rand (10)) + 1;
chomp $div;
if ($div < $int) {
print ("The number I though of is higher than $div, try again?\n");
}
if ($div > $int) {
print ("The number I though of is lower that $div, try again?\n");
}
if ($div == $int) {
print ("Amazing, you've guessed mt number\n");
}
The more straightforward approach would be a while loop.
use strict;
use warnings;
print "Hello, I've thought of a number, do you know what number it is?\n";
sleep (1);
my $int = int(rand (10)) + 1;
print "Try and guess, type in a number between 1 and 10!\n";
while (my $div = <STDIN>) {
chomp $div;
if ($div < $int) {
print "The number I though of is higher than $div, try again?\n";
}
elsif ($div > $int) {
print "The number I though of is lower that $div, try again?\n";
}
else {
print "Amazing, you've guessed mt number\n";
last;
}
}
While (pun intended) your code already is very good (you are using strict and warnings and there are no syntax errors, yay for that!) there are some things I changed, and some more where I would suggest improvement.
But first, let's look at the loop. The program will stay in the while loop as long as the condition is true. Since everything the user can input (even an empty line) is considered true by Perl, this is forever. Which is fine, as there is a condition to exit the loop. It's in the else part of the if. The last statement tells Perl to exit the loop. If the else is not executed, it will go back to the start of the while block and the user has to try again. Forever.
The changes I made:
- You don't need $i as you did not use it
- You used three seperate if statements. Since only one of the three conditions can be true in this case, I merged them into one
- No need for the parens () with print
Suggestions:
- You should name your variables for what they do, not what they are. $int is not a good name. I'd go with $random, or even $random_number. Verbosity is important if you have to come back to your code at a later point.
- There is a function called say that you can enable with use feature 'say';. It adds say "stuff" as an equivalent to print "stuff\n".
Edit:
If you want to add other conditions that do not directly relate to which number the user has entered, you can add another if.
while (my $div = <STDIN>) {
chomp $div;
if ($div eq 'quit') {
print "You're a sissy... the number was $int. Goodbye.\n";
last;
}
if ($div < $int) {
print "The number I though of is higher than $div, try again?\n";
}
elsif ($div > $int) {
print "The number I though of is lower that $div, try again?\n";
}
else {
print "Amazing, you've guessed mt number\n";
last;
}
}
You can also add a check to make sure the user has entered a number. Your current code will produce warnings if a word or letter was is entered. To do that, you will need a regular expression. Read up on them in perlre. The m// is the match operator that works together with =~. The \D matches any character that is not a number (0 to 9). next steps over the rest of the while block and begins with the check of the while condition.
while (my $div = <STDIN>) {
chomp $div;
if ($div =~ m/\D/) {
print "You may only guess numbers. Please try again.\n";
next;
}
# ...
}
Thus, the complete check means 'look at the stuff the user has entered, and if there is anything else than a number in it at all, complain and let him try again'.
use an until loop
my $guessed = 0;
do {
print "Try and guess, type in a number between 1 and 10!\n";
my $div = <STDIN>;
...;
if ($div == $int) {
print ("Amazing, you've guessed mt number\n");
$guessed = 1;
}
} until ($guessed)

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.

Is there a cleaner way to conditionally 'last' out of this Perl loop?

Not really knowing Perl, I have been enhancing a Perl script with help from a friendly search engine.
I find that I need to break out of a loop while setting a flag if a condition comes true:
foreach my $element (#array) {
if($costlyCondition) {
$flag = 1;
last;
}
}
I know that the nicer way to use 'last' is something like this:
foreach my $element (#array) {
last if ($costlyCondition);
}
Of course, this means that while I can enjoy the syntactic sugar, I cannot set my flag inside the loop, which means I need to evaluate $costlyCondition once again outside.
Is there a cleaner way to do this?
you can use a do {...} block:
do {$flag = 1; last} if $costlyCondition
you can use the , operator to join the statements:
$flag = 1, last if $costlyCondition;
you can do the same with the logical && operator:
(($flag = 1) && last) if $costlyCondition;
or even the lower priority and:
(($flag = 1) and last) if $costlyCondition;
at the end of the day, there's no real reason to do any of these. They all do exactly the same as your original code. If your original code works and is legible, leave it like it is.
I agree with Nathan, that while neat looking code is neat, sometimes a readable version is better. Just for the hell of it, though, here's a horrible version:
last if $flag = $costly_condition;
Note the use of assignment = instead of equality ==. The assignment will return whatever value is in $costly_condition.
This of course will not make $flag = 1, but whatever $costly_condition is. But, since that needs to be true, so will $flag. To remedy that, you can - as Zaid mentioned in the comments - use:
last if $flag = !! $costly_condition;
As mentioned, pretty horrible solutions, but they do work.
One thought is to do the loop in a subroutine that returns different values depending on the exit point.
my $flag = check_elements(\#array);
# later...
sub check_elements {
my $arrayref = shift;
for my $ele (#$arrayref) {
return 1 if $costly_condition;
}
return 0;
}
This is possible, but highly not recommended: such tricks decrease readability of your code.
foreach my $element (#array) {
$flag = 1 and last if $costlyCondition;
}

How can I differentiate between 0 and whitespace in Perl?

I have the following piece of code in my program:
$val = chr(someFunction());
if($val == " ")
{
#do something
}
elsif($val == 0)
{
#do something else
}
But whenever 0 is passed to $val, the if part executes instead of the elsif which I expect to get executed.
How can I fix this?
Thank You.
The == operator is used to compare numeric values. If you want to compare strings, you should use the eq operator.
if ($val eq " ") ...
There are several ways to fix this (TIMTOWDI). You could import the looks_like_a_number function from the standard Scalar::Util package:
if (looks_like_a_number($val) and $val == 0) {
#do something
}
You could use the string equality operator
if ($val eq 0) {
#do something
}
If you have Perl 5.10, you could use the smart match operator
if ($val ~~ 0) {
#do something
}
And many more. Which method you use depends heavily on what you are trying to achieve.
If you had warnings enabled, you would have known what the problem was.
Run this:
use strict;
use warnings;
my $val = chr(someFunction());
if($val == " ")
{
#do something
}
elsif($val == 0)
{
#do something else
}
sub someFunction {
return 1;
}
And you get:
C:>test.pl
Argument " " isn't numeric in numeric eq (==) at C:\test.pl line 6.
Argument "^A" isn't numeric in numeric eq (==) at C:\test.pl line 6.
Adding use diagnostics gives us this additional explanation:
(W numeric) The indicated string was fed as an argument to an operator
that expected a numeric value instead. If you're fortunate the message
will identify which operator was so unfortunate.
So, since we don't want numeric eq, we want string eq: eq. If you didn't know that already, you could look in perldoc perlop to read about Equality Operators.
This is a classic example of how using the warnings and strict pragmas saves time.