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

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.

Related

Definition of empty {} after If() statement in eval{} statement

I am currently attempting to document a Perl script in preparation for converting it to .NET. I have no prior experience in Perl before now, however I was managing to get through it with a lot of Google-fu. I have run into a single line of code that has stopped me as I am unsure of what it does. I've been able to figure out most of it, but I'm missing a piece and I don't know if it's really that important. Here is the line of code:
eval { if(defined $timeEnd && defined $timeStart){}; 1 } or next;
I know that defined is checking the variables $timeEnd and $timeStart to see if they are null/nothing/undef. I also believe that the eval block is being used as a Try/Catch block to trap any exceptions. The line of code is in a foreach loop so I believe the next keyword will continue on with the next iteration of the foreach loop.
The part I'm having difficulty deciphering is the {};1 bit. My understanding is that the ; is a statement separator in Perl and since it's not escaped with a backslash, I have no idea what it is doing there. I also don't know what the {} means. I presume it has something to do with an array, but it would be an empty array and I don't know if it means something special when it is directly after an if() block. Lastly, I no idea what a single integer of 1 means and is doing there at the end of an eval block.
If someone could break that line of code down into individual parts and their definitions, I would greatly appreciate it.
Bonus: If you can give me a .NET conversion, and how each Perl bit relates to it, I will most certainly give you my internet respects. Here's how I would convert it to VB.NET with what I know now:
For each element in xmlList 'This isn't in the Perl code I posted, but it's the `foreach` loop that the code resides in.
Try
If Not IsNothing(timeEnd) AND Not IsNothing(timeStart) then
End If
Catch ex as Exception
Continue For
End Try
Next
Ignoring elsif and else clasuses, the syntax of an if statement is the following:
if (EXPR) BLOCK
The block is executed if the EXPR evaluates to something true. A block consists of a list of statements in curly braces. The {} in your code is the block of the if statement.
It's perfectly valid for blocks to be empty (to contain a list of zero statements). For example,
while (s/\s//) { }
is an inefficient way of doing
s/\s//g;
The thing is, the condition in the following has no side-effects, so it's quite useless:
if(defined $timeEnd && defined $timeStart){}
It can't even throw an exception![1] So
eval { if(defined $timeEnd && defined $timeStart){}; 1 } or next;
is equivalent to
eval { 1 } or next;
which is equivalent to[2]
1 or next;
which is equivalent to
# Nothing to see here...
Technically, it can if the variables are magical.
$ perl -MTie::Scalar -e'
our #ISA = "Tie::StdScalar";
tie(my $x, __PACKAGE__);
sub FETCH { die }
defined($x)
'
Died at -e line 4.
I doubt the intent is to check for this.
Technically, it also clears $#.
eval{} returns result of last expresion (1 in your example) or undef if there was an exception. You can write same code as,
my $ok = eval {
if (defined $timeEnd && defined $timeStart){};
1
};
$ok or next;
From perldoc -f eval
.. the value returned is the value of the last expression evaluated inside the mini-program; a return statement may be also used, just as with subroutines.
If there is a syntax error or runtime error, or a die statement is executed, eval returns undef in scalar context or an empty list in list context, and $# is set to the error message

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

What is the result of Perl's &&?

When I try this:
$a = 1;
$b = 2;
print ($a && $b) . "\n";
The result is 2. Why?
Quote perlop:
The "||", "//" and "&&" operators
return the last value evaluated
(unlike C's "||" and "&&", which
return 0 or 1).
The resulting 2 is considered true by Perl, so that when you use the && operator in a logical condition, everything works as expected. The added bonus is that you can use the logical operators in other contexts as well:
sub say_something {
say shift || 'default';
}
say_something('foo'); # prints 'foo'
say_something(); # prints 'default'
Or even as flow modifiers:
my $param = shift || die "Need param!";
-f $file && say "File exists.";
In the last two examples it’s good to realize that they could not work if the && and || operators did not short-circuit. If you shift a true value in on first line, there is no point evaluating the right side (die…), since the whole expression is true anyway. And if the file test fails on the second line, you don’t need to evaluate the right side again, since the overall result is false. If the logical operators insisted on evaluating the whole expression anyway, we could not use them this way.
That's how the && operator works: if the left-hand argument evaluates as true, the value of the expression is that of the value of the right-hand argument. This is covered on the perlop page.
However, you've also let yourself open to a much more subtle problem. You'll find that the newline doesn't get printed. This is because if you put an expression in brackets after print (or any other function name) the arguments passed to print are just those in the brackets. To get notice of this, make sure you switch on warnings. Put these lines at the top of each program:
#!/usr/bin/perl -w
use strict;
until you understand them enough to decide for yourself whether to continue with them. :-)
In your case, you'll get this:
print (...) interpreted as function at p line 7.
Useless use of concatenation (.) or string in void context at p line 7.
Because the && operator evaluates the right operand and returns the result when the left operand evaluates to true.

How can I call a post-statement conditional on multiple statements?

$df{key} =10 ; return ; if $result == 10 ;
gives me an error. How can I achieve this?
The post-statement form of if only works with single statements. You will have to enclose multiple statements in a block after the if condition, which itself needs to be enclosed in parentheses:
if ( $result == 10 ) {
$df{key} = 10;
return;
}
In this case, it is possible to combine the two statements with a post-statement conditional. The idea here is to combine the two statements in one by performing a Boolean evaluation.
However, this is not a good idea in general as it may short-circuit and fail to do what you expect, like when $df{key} = 0:
$df{key} = 10 and return if $result == 10;
From perlsyn:
In Perl, a sequence of statements that defines a scope is called a block
... generally, a block is delimited by curly brackets, also known as braces. We will call this syntactic construct a BLOCK.
The following compound statements may be used to control flow:
if (EXPR) BLOCK
if (EXPR) BLOCK else BLOCK
if (EXPR) BLOCK elsif (EXPR) BLOCK ... else BLOCK
You can group the statements into a do BLOCK and use a conditional
statement modifier on that compound statement.
do { $df{key} = 10; return } if $result == 10;
Unlike the and construct posted by Zaid, this is not ambiguous. You
should, however, think twice before using a conditional statement
modifier. Especially mixing if/unless statements with
if/unless statement modifiers reduces readability of your code.
The main case where in my opinion the statement modifiers make sense
are uncomplicated error paths, i.e.:
croak "foo not specified" unless exists $args{foo};
The comma operator allows one to chain together multiple statements into an expression, after which you can include the conditional:
$df{key} = 10, return if $result == 10;
I use this construct quite often when checking for error conditions:
for my $foo (something...)
{
warn("invalid thing"), next unless $foo =~ /pattern/;
# ...
}

How can I read from a method that returns a filehandle in Perl?

I have an object with a method that returns a filehandle, and I want to read from that handle. The following doesn't work, because the right angle bracket of the method call is interpreted as the closing angle bracket of the input reader:
my $input = <$object->get_handle()>;
That gets parsed as:
my $input = ( < $object- > ) get_handle() >;
which is obviously a syntax error. Is there any way I can perform a method call within an angle operator, or do I need to break it into two steps like this?
my $handle = $object->get_handle();
my $input = <$handle>;
You could consider spelling <...> as readline(...) instead, which avoids the problem by using a nice regular syntax instead of a special case. Or you can just assign it to a scalar. Your choice.
You have to break it up; the <> operator expects a typeglob like <STDIN>, a simple scalar variable containing a reference to a filehandle or typeglob like <$fh>, or an argument for the glob() function like <*.c>. In your example, you're actually calling glob('$object-').
<> is actually interpreted as a call to readline(), so if you really want to you could say my $input = readline( $object->get_handle() ); I'm not sure that's cleaner though, especially if you're going to read from the handle more than once.
See http://perldoc.perl.org/perlop.html#I%2fO-Operators for details.
my $input = readline($object->get_handle());
or
use IO::Handle;
my $input = $object->get_handle()->getline();
You won't be able to use the <...> operator here to read a file handle, because anything more complex than <bareword> or <$scalar> is interpreted as a glob(...) call, so none of the usual disambiguation tricks will work here. The <HANDLE> operator is syntactic sugar for readline HANDLE, so you could write it this way:
my $input = readline $object->get_handle;
However, if you will be doing this in a loop, it will be far more efficient to cache the handle in a scalar. Then the <...> operator will work as you expected:
my $handle = $object->get_handle;
while (my $input = <$handle>) {
...
}