perl: loop over function that returns array or undef - perl

I am looking for the proper perl-ism for this issue. I can work around it but just have to ask...
I am using HTML::TreeBuilder and am using the look_down method. This returns an array or scalar depending on context and returns undef if no matching tag is found. Cool.
I want to do the following:
foreach my $tag ( #{ $head->look_down('_tag', 'link') } ) {
...
}
but if there is no link tag the function returns undef and generates the error Can't use an undefined value as an ARRAY reference at myCGI.cgi line ###. So I try this modification:
foreach my $tag ( #{ $head->look_down('_tag', 'link') || [] } ) {
...
}
My thought was that if the method returns undef then it will get changed into an empty array. This works when there are no link tags. But, if there is at least one expected tag then there is an error: Not an ARRAY reference at myCGI.cgi line ###.
Do I need to just bite the bullet and break the method call out of the loop and check for undef before entering the loop?

"returns an array" is sometimes mentioned in documentation but is incorrect; perl subroutines always return lists (though in scalar context it will be a list with only one element).
It does not return undef in list context, it returns an empty list (return with no arguments returns an empty list in list context and undef in scalar context). You can just loop over the returned values with no #{ } required:
foreach my $tag ( $head->look_down('_tag', 'link') ) {
...
}

Related

Perl error, cant use defined(#array). How can i fix this?

I am getting this error -> "Can't use 'defined(#array)' (Maybe you should just omit the defined()?)"
On this line of code:
if ( defined( #{ $linkbot{"$nroboton"} } ) ) {
How can i fix this?
defined tests whether a scalar value is undef, therefore it's nonsensical on an array. You can test whether the scalar is defined before using it as an arrayref, or if you're trying to test if the array is empty, just removed the defined() as the error message says.
# if this hash value is defined
if (defined $linkbot{$nroboton}) {
# if this referenced array has elements
if (#{ $linkbot{$nroboton} }) {
Use define on the variable $nroboton itself, and/or if (#{$linkbot{$nroboton}}) for the anonymous array whose reference is the value for that key, as explained.
Once you need to check on any of that it stands to reason that you may well also need to test whether there is a key $nroboton in the hash %linkbot, with exists
if ( exists $linkbot{$nroboton} ) { ... } # warning if $nroboton undef
so altogether
if (defined $nroboton and exists $linkbot{$nroboton}) { ... }
and now you can check and work with the data in the arrayref, #{$linkbot{$nroboton}}.
Note that there is no need to double-quote that variable; it will be evaluated.

Perl get parameter datatype

Am trying to make a subroutine that replaces data depending on datatype: the problem is i can't get the datatype of the parameter, i used this:
sub replace {
my ($search, $replacement, $subject) = #_;
if (ref($search) eq "HASH") {
print "r is a reference to a HASH.\n";
}
elsif (ref($search) eq "SCALAR") {
print "r is a reference to a SCALAR.\n";
}
elsif (ref($search) eq "ARRAY") {
print "r is a reference to a ARRAY.\n";
}
}
my $str = "Foo";
my #arr = ("Foo");
replace($str);
replace(#arr);
But none works. am really new to perl
ref() takes a reference to something, not the something itself. Here:
replace($str);
replace(#arr);
...you are sending in the something directly. Send in a reference to the something instead, by putting a \ in front of it (which says, "take a reference to this something"):
replace(\$str);
replace(\#arr);
Output:
r is a reference to a SCALAR.
r is a reference to a ARRAY.
Note also that in your replace() function, in this line:
my ($search, $replacement, $subject) = #_;
You are effectively asking for a scalar value as the thing to search, so passing in a list (array, hash etc) will clobber $replacement and $subject if the passed in list has more than one element, so you may want to do something like this to ensure you're getting the proper params, and nothing is clobbered unexpectedly:
sub replace {
my ($search, $replacement, $subject) = #_;
die "first arg must be a ref\n" if ! ref $search;
Of course, you can do further argument checking, but this'll ensure that the first parameter can only be a reference to something. Instead of die(), you can also just return so the program doesn't crash, or print or warn and then return.
It is not stated what you want to do with it, but here's what is wrong with what you show.
The ref function shows the datatype of the reference subtmitted to it, or it returns an empty string if its argument isn't a reference at all.
So to get the expected behavior you should do
replace(\$str);
replace(\#arr);
Also, you need to add the test to your function
else (not ref $search)
for when a submitted string is not a reference.
For completeness, I should also point out an issue, explained in the answer by stevieb. When you pass an array to a function, it receives it as a flat list of arguments. With your function you clearly do not want replace(#arr). They are assigned to your list of scalar variables in order, one element to each. (As soon as there is an array variable it all goes into it.) See, for example, this post.

Perl function: accept hash, array, or scalar as parameter

I have the following sub-routine.
sub Function{
my $ref = \($_[0]);
if(ref($ref) eq 'SCALAR'){
. . .
}
}
I am trying to get it to work such that passing a list, scalar or hash to it, it converts that into a reference and depending upon whether that reference is of type ARRAY, SCALAR or HASH, different actions are performed.
It could be called as
Function(%a)
Function($a)
Function(#a)
How can I make $ref be a reference to whatever is passed to the Function? My current approach isn't working.
Function(%a)
Function($a)
Function(#a)
You can't do what you are asking. There is no way inside Function to determine whether an array or a hash was passed, because, in fact, you can't pass an array or a hash to a sub in the first place. You can only pass a list; the hashes and arrays are converted to lists in the sub calls above.
You should pass a reference to the function in the first place:
Function(\%a)
Function($a)
Function(\#a)
Then you can check what it is easily:
sub Function {
my $param = shift;
if (not ref $param) {
...
} elsif (ref $param eq 'HASH') {
...
} elsif (ref $param eq 'ARRAY') {
...
}
You can use a prototyped function. Although Perl 5 prototypes are a total mess, they can be used quite well in this case. As I understand them the prototype
sub Function (\[$#%]) {...}
should solve your problem, and the reference be in $_[0].
The big disadvantage is that the variable you pass has to start with the $, #, or % character, so you can't call Function with a constant argument directly:
Function(1)
fails, but
my $temp = 1;
Function($temp)
works.
Your approach failes, because #_ is a list, and all elements are scalars.
Why not just check to see if the argument passed in is a reference and act accordingly?
sub gimme_a_ref
{
my $arg=shift;
if(ref $arg ne ref "")
{
return $arg;
}
else
{
return \$arg;
}
}

perl function ignores undef arguments

I'm passing some arguments to a function, one of which might be undefined.
$a = ($config->function(param('a'),'int'));
my module contains a function which looks like this:
sub function{
my $self = $_[0];
my $t = $_[1];
my $type = $_[2];
print "$self,$t,$type<br/>";
}
I've tried with shift instead of the #_ syntax, but there's no change.
The problem is that if $config->function is called with an undefined param('a') it prints like this:
MY_FUNC=HASH(0x206e9e0),name,
it seems that $t is being set to the value of what $type should be and the undef is being ignored completely.
undef is perfectly valid in a parameter list. I suspect that the problem here is that the param function is returning an empty list, rather than undef.
sub function {
no warnings 'uninitialized';
print join "/", #_;
}
function(undef,"foo"); # outputs "/foo"
function((), "foo"); # outputs "foo"
In the argument list to function, the param function is evaluated in list context.
sub param1 {
return; # undef in scalar context, but empty list in list context
}
sub param2 {
return undef; # scalar ctx: undef, list ctx: list with one undef elem
}
function(param1(), "foo"); # param1() -> () ... outputs "foo"
function(param2(), "foo"); # param2() -> (undef) ... outputs "/foo"
A workaround is to make sure that your param function is evaluated in scalar context.
function(scalar param1(), "foo"); # now outputs "/foo"
Note that actually saying return undef in your Perl subroutines is considered by some to be a code smell.
The CGI param method is correctly returning an empty list in the case of an error. This is standard behaviour for Perl subroutines and methods, which return a false value in the case of an erorr.
As #mob says, an explicit return undef is wrong precisely because it returns a one-element list in list context. This is a true value and implies success, which would break a lot of code if it was changed
All you need to do is change your code to extract the parameter separately
my $param = param('a');
$config->function($param, 'int'));
or if you're really keen to put the call in the parameter list then you can write
$config->function(scalar param('a'), 'int'));
or
$config->function(param('a') // undef, 'int'));
[I started this as a comment but it got too long].
Inside an argument list, arguments are evaluated in list context. Two answers have already noted that, but I think you missed it for all of the other things they were talking about.
Beyond that, param has to handle at least three cases, and the interface Lincoln chose, although sometimes annoying, is a good enough way to handle them.
The query parameter a does not appear at all:
b=1
The query parameter a appears, but with no value:
a=&b=1
The query parameter a appears with a value:
a=1&b=1

Why does this statment return true

Why does this statement,
if (! $ssh_options{user}) {
delete $ssh_options{user};
}
return true, but this statement
if ($ssh_options{user} eq 'undef') {
delete $ssh_options{user};
}
give me the error,
Use of uninitialized value $ssh_options{"user"} in string eq at analyze.pl line 230.
I thought you always had to have something for perl to compare the value of the variable being compared.
** Update **
# Quick Joe Smith
I cannot base my comparison on whether the hash keys exist or not because the values for this hash
sub ssh_connect {
my $host = shift;
my %ssh_options = (
port => shift,
user => shift,
password => shift
);
Come from this function which calls the sub routine
if ((exists $config_file{user}) && (exists $config_file{password})) {
my $vmware_user = $config_file{user};
my $vmware_password = $config_file{password};
ssh_connect($vmware_host, $vmware_port, $vmware_user, $vmware_password);
} else {
ssh_connect($vmware_host, $vmware_port);
}
Perl hash slots have a number of ways they can be false. If the key has never been filled, the exists function will return false. If the key exists but contains an undefined value, exists will be true but the defined function will return false. In normal boolean context, as given by an if statement, a hash key that was never filled, or is undefined, '', 0 or anything that reduces to those will be false.
From your update, it sounds like what you want is to use defined
delete $ssh_options{user} unless defined $ssh_options{user};
Use exists for checking hash keys.
if (exists $ssh_options{user}) {
...
}
However, in your case, it seems as though you're checking for existing keys whose values may be undefined, in which case:
if (defined $ssh_options{user}) {
...
}
The above may be what you want.
As a sidenote, checking to see if the value of any variable is defined in the way you described:
if ($something eq 'undef') {
...
}
Is wrong. You're checking to see if the variable contains the string "undef". What you need in those situations is:
unless (defined $something) {
...
}
But if $ssh_options{"user"} is uninitialized then it it doesn't have a value to compare to.
With Perl, undefined evaluates to false in Boolean context. So your first test 'works' as you expect. However, it might introduce a bug if zero or an empty string is a legitimate value for $ssh_options{user} to hold, if you don't wish to treat that the same as undef. That's because your first test doesn't differentiate between false as a value, and false as undefined. As far as Perl's Boolean evaluation goes, all of the following are "false": 0, '' (empty string), undef, or an empty list.
Your second snippet of code fails because undef should be a bareword, not a quoted string. 'undef' as a quoted string IS a value, which would actually evaluate to 'true' in Boolean tests. Because you're comparing an actual value to your hash element, Perl warns you that you're comparing something to an undefined value. In this case, the undefined value is $ssh_options{user}. It's actually good that Perl is warning you; it's given you a clue as to what you're doing wrong.
If you really want to test whether $ssh_options{user} is defined, use the defined() function. If you want to test whether $ssh_options{user} exists, use the exists() function.