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.
Related
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.
I am a little confused as which way to test parameters. Here are two examples from source code posted below. First is this
if(!defined($DBHdl) || !defined($acct_no));
the way to test for undefined parameters?
Second, after assigning to a hashref
$ptMtrRecRef = $ptSelHdl->fetchrow_hashref;
is the best way to test for $ptMtrRecRef being defined to use
if(!$ptMtrRecRef)
or
if(!defined($ptMtrRecRef))?
###############################################################################
# Returns count of meters per account number.
# $PkNam -- package name discarded
# $DBHdl -- ICS database handle
# $acct_no -- water account number
sub mgbl_get_meter_count
{
my ($PkNam, $DBHdl, $acct_no) = #_;
die("mgbl_get_meter_count passed undef handles.\n")
if(!defined($DBHdl) || !defined($acct_no));
my $ptSelHdl;
my $ptMtrRecRef;
my $sql_statement =
"select count(*) from meter m where m.acct_no = ".$acct_no.";";
$ptSelHdl = $DBHdl->prepare($sql_statement);
die("Cannot prepare select count(*) from meter m\n")
if(!$ptSelHdl || !$ptSelHdl->execute);
$ptMtrRecRef = $ptSelHdl->fetchrow_hashref;
return $ptMtrRecRef;
}
$sth->fetchrow_hashref will either return undef or a reference to a hash. As such
if (defined($row))
and
if ($row)
are equivalent here. (undef is false, and reference is always true.) I opt for the simpler alternative.
Same idea for $dbh->prepare.
In the case of the code you posted, I would also do as ikegami said, and use the shorter form.
There are occasions when that isn't suitable, however, for example if a variable could have a legitimate value that would be treated as false if simply used in a true/false test. For example:
my $value = 0;
print "defined\n" if defined $value; # prints 'defined'
print "true\n" if $value; # does not print anything
Well , in perl script language, defined($a) is just a sub routine to test if $a is "undef",nothing else. So ,you will ask ,what is undef?
To be accurate, it is a perl subroutine ,the same as defined.But when it has no parameter, it can be considered as a perl-special scalar . For example , when you pop a value from an empty array ,it will return an undef.When you call subroutine "undef $b",then $b will become undef($b must be an left value),nothing else. Only in this case, defined($b) will return false.But if $c is an empty string like "" ,number zero ,or string "0" ,defined($c) will still return true;
But if you use a simple boolean expression instead of defined,it becomes totally different. A simple Boolean test will not distinguish among undef, zero, the empty string, and "0" .So , it absolutely depends on your pratical requirement when determining using defined() or just a boolean test.
In Perl, can one specifiy data types for the parameters of subroutines? E.g. when using a dualvar in a numeric context like exit:
use constant NOTIFY_DIE_MAIL_SEND_FAILED => dualvar 3, 'NOTIFY_DIE_MAIL_SEND_FAILED';
exit NOTIFY_DIE_MAIL_SEND_FAILED;
How does Perl in that case know, that exit expects a numeric parameter? I didn't see a way to define data types for the parameters of subroutines like you do it in Java? (where I could understand how the data type is known as it is explicitely defined)
The whole point of the dualvar is that it behaves as a number or text depending on what you want. In cases where that's not obvious (to you more importantly than to perl) then make it clear.
exit 0 + NOTIFY_DIE_MAIL_SEND_FAILED;
As for explicitly typing parameters, that's not something built in. Perl is a much more dynamic language than Java so it's not common to check/force the type of every parameter or variable. In particular, a perl sub can accept different numbers of parameters and even different structures.
If you want to validate parameters (for an external API for example) try something like Params::Validate
In addition, Moose and Moo allow a certain level of attribute typing and even coercion.
In Perl, scalars are both numeric and stringy at the same time. It is not the variables themselves that distinguish between strings and numbers, but the operators you work with. While the addition + only uses a number, the concatenation . only uses strings.
In more strongly typing languages, e.g. Java, the addition operator doubles as addition and concatenation operator, because it can access type information.
"1" + 2 + 3 is still sick in Java, whereas Perl can cleanly distinguish between "1" + 2 + 3 == 6 and "1" . 2 . 3 eq "123".
You can force numeric or stringy context of a variable by adding 0 or concatenating the empty string:
sub foo {
my ($var) = #_;
$var += 0; # $var is numeric
$var .= ""; # $var is stringy now
}
Perl is quite different from Java in that - Perl is dynamically typed language, because it does not requires its variables to be typed at compile time..
Whereas, Java is statically typed (as you know already)
Perl determines the type of the variable depending upon the context it is used..
There can be only two context: -
List Context
Scalar Context
And the context is defined by the operator or function that is used..
For EG:-
# Define a list
#arr = qw/rohit jain/;
# Define a scalar
$num = 2
# Here perl will evaluate #arr in scalar context and take its length..
# so, below code will evaluate to : - value = 2 / 2
$value = #arr / $num;
# Here since it is used with a foreach loop, #arr will be taken as in list context
foreach (#arr) {
say $_;
}
# Above foreach loop will output: - `rohit` \n `jain` to the console..
You can force the type by:
use Scalar::Util qw(dualvar);
use constant NOTIFY_DIE_MAIL_SEND_FAILED => dualvar 3, 'NOTIFY_DIE_MAIL_SEND_FAILED';
say NOTIFY_DIE_MAIL_SEND_FAILED;
say int(NOTIFY_DIE_MAIL_SEND_FAILED);
output:
NOTIFY_DIE_MAIL_SEND_FAILED
3
How does Perl in that case know, that exit expects a numeric parameter?
exit expect a number as is part of its specification and its behaviour is kind of undefined if you pass it a non-integer value (i.e. you should not do it.
Now, in this particular case, how does dualvar manages to return either value type depending of the context?
I don't know how Scalar::Util's dualvar is implemented but you can write something similar with overload instead.
You certainly can modify the behaviour for a blessed object:
#!/usr/bin/env perl
use strict;
use warnings;
{package Dualvar;
use overload
fallback => 1,
'0+' => sub { $_[0]->{INT_VAL} },
'""' => sub { $_[0]->{STR_VAL} };
sub new {
my $class = shift;
my $self = { INT_VAL => shift, STR_VAL => shift };
bless($self,$class);
}
1;
}
my $x = Dualvar->new(31,'Therty-One');
print $x . " + One = ",$x + 1,"\n"; # Therty-One + One = 32
From the docs, it seems that overload actually changes the behaviour within the declaration scope so you should be able to change the behaviour of some common operators locally for any operand.
If exit does use one of those overloadable operations to evaluate its parameter into a integer then this solution would do.
I didn't see a way to define data types for the parameters of subroutines like you do it in Java?
As already said by others... this is not the case in Perl, at least not at compilation time, except for subroutine prototypes but these don't offer much type granularity (like int vs strings or different object classes).
Richard has mentioned some run-time alternatives you may use. I personally would recommend Moose if you don't mind the performance penalty.
What Rohit Jain said is correct. A function that wants input to follow certain rules simply has to explicitly check that the input is valid.
For example
sub foo
{
my ($param1,$param2) = shift;
$param1 =~ /^\d+$/ or die "Parameter 1 must be a positive integer.";
$param2 =~ /^(bar|baz)$/ or die "Parameter 2 must be either 'bar' or 'baz'";
...
}
This may seem like a pain, but:
The extra flexibility gained generally outweighs the work involved in doing this.
Simply having the correct data type is often not enough to ensure that you valid input, so you end up doing a lot this anyway even in a language like Java.
This question has been asked about PHP both here and here, and I have the same question for Perl. Given a function that returns a list, is there any way (or what is the best way) to immediately index into it without using a temporary variable?
For example:
my $comma_separated = "a,b,c";
my $a = split (/,/, $comma_separated)[0]; #not valid syntax
I see why the syntax in the second line is invalid, so I'm wondering if there's a way to get the same effect without first assigning the return value to a list and indexing from that.
Just use parentheses to define your list and then index it to pull your desired element(s):
my $a = (split /,/, $comma_separated)[0];
Just like you can do this:
($a, $b, $c) = #array;
You can do this:
my($a) = split /,/, $comma_separated;
my $a on the LHS (left hand side) is treated as scalar context. my($a) is list context. Its a single element list so it gets just the first element returned from split.
It has the added benefit of auto-limiting the split, so there's no wasted work if $comma_separated is large.
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>) {
...
}