Non-defined return of sub - perl

How can I avoid that a variable gets touched by a return of a subfunction. I wrote following code snippet
#!/usr/bin/perl
use strict;
use warnings;
my $a = "init";
sub funct{
my $var;
#$var = 1;
return $var if defined $var;
}
my $tmp = funct;
$a = $tmp if defined $tmp;
print "$a\n";
and I don't want the value of $a be changed from it's initial init if $var isn't defined in the subfunction.
Where is the error or is there a better way to solve this problem?
Greetings

The return $foo if $bar is equivalent to $bar and return $foo. Therefore, when $bar is false, then this statement evaluates to the value of $bar. As subroutines return the value of the last executed statement, your sub returns either $var if it is defined, or a false value, if not.
You can either go the explicit route, and put another return there:
# all branches covered
return $var if defined $var;
return;
This is silly, and equivalent to return $var.
Now the false value that your sub returns is in fact defined, so it is assigned to $a. You could test for truth instead:
$a = $tmp if $tmp;
… but that opens another can of worms.
Return values are extraordinary bad at telling us whether they are the wanted return value, or an error indicator. There are two clean ways around that:
Return a second value that tells us whether the function exited OK:
sub func {
my $var;
return (1, $var) if defined $var;
return (0); # not strictly needed
}
my ($ok, $tmp) = func();
$a = $tmp if $ok;
(basically, the comma-ok idiom as seen in Golang)
Return a container that has to be destructured to obtain the actual return value. A possibility is to either return undef (or something false) on error, or a scalar reference to the value when such a value exists:
sub func {
my $var;
return \$var if defined $var;
return undef; # actually not needed
}
my $tmp = func();
$a = $$tmp if defined $tmp;
(basically, a Maybe type as seen in Haskell)
Here is a way to use that without obious temp vars:
($a) = map { $_ ? $$_ : () } func(), \$a;

You can avoid temporary variable,
$a = funct() // $a;

Running this program in the Perl debugger shows that $tmp is set to an empty string, which evaluates to true with the defined function. This is why the conditional that sets $a evaluates to true:
$ perl -d
Loading DB routines from perl5db.pl version 1.33
Editor support available.
Enter h or `h h' for help, or `perldoc perldebug' for more help.
use strict;
use warnings;
my $a = "init";
sub funct{
my $var;
#$var = 1;
return $var if defined $var;
}
my $tmp = funct;
$a = $tmp if defined $tmp;
print "$a\n";
__END__
main::(-:4): my $a = "init";
DB<1> x $a
0 undef
DB<2> n
main::(-:12): my $tmp = funct;
DB<2> x $a
0 'init'
DB<3> x $tmp
0 undef
DB<4> n
main::(-:14): $a = $tmp if defined $tmp;
DB<4> x $tmp
0 ''
To fix, simply return $var, without the if defined $var. This will set $tmp to undef

It looks like you're trying to detect error scenarios by returning undef. In addition to #amon's suggestions, you could die out of funct when an error occurs:
#!/usr/bin/perl
use strict;
use warnings;
my $a = "init";
sub funct{
my $var;
...
if ($something_went_wrong) {
die 'something bad';
}
...
return $var;
}
eval {
$a = funct;
1;
} or do {
# log/handle the error in $#
};
print "$a\n";

Related

Confusion about using perl #_ variable with regex capture

When I using following code, subroutine f can't print #_ correctly for the substitution of $tmp before it, which is explainable.
use strict;
use warnings;
sub f {
# print("args = ", #_);
my $tmp = "111";
$tmp =~ s/\d+//;
print("args = ", #_);
}
"dd11ddd" =~ /(?<var>\d+)/;
f($+{"var"});
But when I uncomment the first print statement, then both print could give the correct #_, which makes me confused, why the capture group hasn't been overwrite. Or just some underlay mechanism of perl I don't know? Please help, thanks.
When I pass capture group into perl subroutine, the capture group hasn't been overwritten as expected.
I want to know why this could happen and how to explain it correctly.
Perl arguments are passed by reference.
sub f {
$_[0] = "def";
}
my $x = "abc;
say $x; # abc
f( $x );
say $x; # def
%+ is affected by $tmp =~ s/\d+//, and thus so is $_[0]. We don't usually run into problems because we usually make an explicit copy of the arguments.
sub f {
my $y = shift;
$y = "def";
}
my $x = "abc";
say $x; # abc
f( $x );
say $x; # abc
Passing a copy of the scalar would also avoid the problems.
sub f {
$_[0] = "def";
}
my $x = "abc";
say $x; # abc
f( "$x" );
say $x; # abc
The above explains why you get weird behaviour and how to avoid it, but not why accessing $_[0] before the substitution seems to fix it. Honestly, it doesn't really matter. It's some weird interaction between the magical nature of %+, the implicit localization of %+, the optimizations to avoid needless implication localizations of %+, and the way localization works.

Dereferencing Conditionally in Perl

I have a scalar that may or may not be a reference to an array. If it is a reference to an array, I would like to dereference it and iterate over it. If not, I would like to treat it as a one-element array and iterate over that.
my $result = my_complicated_expression;
for my $value (ref($result) eq 'ARRAY' ? #$result : ($result)) {
# Do work with $value
}
Currently, I have the above code, which works fine but feels clunky and not very Perlish. Is there a more concise way to express the idea of dereferencing a value with fallback behavior if the value is not what I expect?
Just force it before the loop.
Limited, known ref type
my $result = *some function call* // [];
$result = [$result] if ref $result ne 'ARRAY';
for my $val ( #$result ){
print $val;
}
Ref type unknown
#!/usr/bin/perl
use 5.012;
use strict;
no warnings;
sub array_ref;
my $result = [qw/foo bar foobar/];
# $result = 'foo'; # scalar test case
# $result = {foo=>q{bar}}; # hash test case
$result = array_ref $result;
for my $val ( #$result ){
say $val;
}
sub array_ref {
my $ref = shift;
given(ref $ref){
$ref = [%$ref] when('HASH');
$ref = [$ref] when(['SCALAR','']);
when('ARRAY'){}
default {
die 'Did not prepare for other ref types';
}
}
return $ref;
}
This is for demo purposes (you shouldn't use given/when in production code), but shows you could easily test for the ref type and cast a new response. However, if you really don't know what type of variable your function is returning, how are you sure it's even a reference. What if it was an array or hash?
Being perl, there's going to be several answers to this with the 'right' one being a matter of taste - IMHO, an acceptable shortening involves relying on the fact that the ref function returns the empty string if the expression given it is scalar. This means you don't need the eq 'ARRAY' if you know there are only two possibilities (ie, a scalar value and an array ref).
Secondly, you can iterate over a single scalar value (producing 1 iteration, obviously), so you don't have to put the $result in parentheses in the "scalar" case.
Putting these two small simplifications togeather gives;
use v5.12;
my $result1 = "Hello World";
my $result2 = [ "Hello" , "World" ];
for my $result ($result1, $result2) {
for my $value ( ref $result ? #$result : $result) {
say $value ;
}
}
which produces;
Hello World
Hello
World
There's likely to be 'fancier' things you can do, but this seems a reasonable compromise between being terse and readable. Of course, YMMV.
I see that I'm late to this, but I can't help it. With eval and $#, and the comma operator
my $ra = [ qw(a b c) ];
my $x = 23;
my $var = $ra;
# my $var = $x; # swap comment to test the other
foreach my $el ( eval { #{$var} }, $# && $var )
{
next if $el =~ /^$/; # when #$var is good comma adds empty line
print $el, "\n";
}
Prints a b c (one per line), if we swap to my $var = $x it prints 23.
When $var has the reference, the $# is empty but the comma is still executed and this adds an empty line, thus the next in the loop. Alternatively to skipping empty lines one can filter them out
foreach my $el ( grep { !/^$/ } eval { #{$var} }, $# && $var )
This does, in addition, clean out empty lines. However, most of the time that is desirable.
sub deref {
map ref($_) eq 'ARRAY'? #$_ : ref($_) eq 'HASH'? %$_ : $_, #_
}
sub myCompExpr {
1, 2, 3, [4, 5, 6], {Hello => 'world', Answer => 42}
}
print $_ for deref myCompExpr

Inconsistent behavior concerning "Can't use an undefined value as an ARRAY reference"

Why does the following code:
use strict;
use warnings;
no warnings 'uninitialized';
use Data::Dumper;
my $user;
my #data = #{$user->{ENTERPRISE}}; # Error on this line
print Dumper($user), qq{Done!\n};
Throw the error "Can't use an undefined value as an ARRAY reference", while the following code:
use strict;
use warnings;
no warnings 'uninitialized';
use Data::Dumper;
my $user;
foreach my $enterprise(#{$user->{ENTERPRISES}}) {
print qq{Enterprise:}, $enterprise;
}
print Dumper($user), qq{Done!\n};
Does not throw anything, but instead returns:
$VAR1 = {
'ENTERPRISES' => []
};
Done!
Both have the offending code in them, but only one is throwing the error.
Possible Answer: Perl's autovivification?
Am I on the right track here? Thanks for your input.
Yes, what happened in the second case is called autovivification, and it only happened in the second case because autovivification only happens for lvalues[1].
So
#{ $x } = $y;
means
#{ $x //= [] } = $y;
but
$y = #{ $x };
doesn't mean
$y = #{ $x //= [] };
Keep in mind that foreach aliases its loop variable to each element of the list over which it iterates, so those elements are evaluated as lvalues.
Autovivification is documented in perlref, and you can control autovivification through the autovivification pragma.
If the final deferencing is followed by an indexing (e.g. ->[] or ->{}), the reference expression itself is evaluated as an lvalue even if the indexed whole isn't. That means that $y = $x->[0]; and $y = ${ $x }[0]; can autovivify $x even if $y = #{ $x }; won't.

Perl - subroutine to translate variables

I wrote the following subroutine:
sub MakeNan {
my $n = $_;
if ( $n !~ /^Positive|^Negative/ ) {
return "N/A";
}
else { return "$n"; }
}
I have been calling it in the following context:
open ( FILE, $file);
while (<FILE>) {
chomp;
my #a = split("\t", $_);
my $hr = $a[55];
$hr = &MakeNan($hr);
print "$hr\n";
}
close FILE;
Unfortunately, it returns "N/A" for every value it is given despite the fact that there are many instances of values that should return either "Positive..." or "Negative..."
I don't understand what I am doing wrong to make the subroutine return "N/A" each time.
There are several mistakes. $n doesn't contain your argument because the default variable is not your argument. Your regex is wrong. Do this instead:
sub make_nan {
my ($n) = #_; # or: my $n = shift;
return $n =~ /^(Positive|Negative)/ ? $n : 'N/A';
}
And drop the & when calling your function.
But then, you don't need a subroutine since all you need is a ternary operator.
Since items passed into a subroutine are passed thru #_, your first line in sub MakeNan should be:
my $n = $_[0];
Or, since there is more than one way to do it, you could also make a scalar reference in the first line of the subroutine to $hr like this.
my $hr_ref = \$hr;

How to use a Perl global hash

I'm having a problem with Perl. It's giving me an error that I don't understand.
This is my program
our $neighbor = {};
$$neighbor{'a'} = 'b';
print Dumper($vizinho);
sub keyboard{
my ($a,$b) = #_;
return 4 if ($a eq $b);
return 2 if $neighbor{$a}{$b};
return -1;
}
And my error is
Variable "%vizinho" is not imported at t/DistStr.t line 30.
Global symbol "%vizinho" requires explicit package name at t/DistStr.t line 30.
Execution of t/DistStr.t aborted due to compilation errors.
# Looks like your test exited with 255 just after 1.
What I want to do is
use Alinhamento;
$neighbor = ... # Something which will create an hash where
# $neighbor{$x}{$y} exists if the letter $x is neighbour of $y
sub keyboard {
my ($a, $b) = #_;
return 4 if ($a eq $b);
return 2 if $neighbor{$a}{$b}; #And use that $neighbor on that function
return -1;
}
But I don't know how to make that. May I have some advice please?
There are a number of approaches, depending on specifics that are not apparent in the context provided. Here is one simple approach loosely based on your original posting:
use Data::Dumper;
our %neighbor;
$neighbor{'a'}{'b'} = 1;
print Dumper(\%neighbor);
sub keyboard{
my ($a, $b) = #_;
return 4 if ($a eq $b);
return 2 if $neighbor{$a}{$b};
return -1;
}
EDIT: Renamed $vizinho to %neighbor. Thanks #Borodin.
Note $a and $b are a little special in Perl. See the documentation for sort.
You must start every Perl program with
use strict;
use warnings;
and many problems will resolve themselves.
You use package (global) variables in the same way as any other variable. In fact, if you have been used to not using strict, then all of your variables have probably been package variables.
Update
To set element x, y of your hash so that it exists and is true, you should write
$vizinho->{$x}{$y} = 1;
Your problem seems to be with how to use references. You declare
our $vizinho = {}
and then try to assign to it using
$$vizinho{'a'} = 'b'
which is wrong. You could write
${$vizinho}{'a'} = 'b'
or, much better
$vizinho->{a} = 'b'
You also shouldn't ever use $a or $b. Your code should look like this
use strict;
use warnings;
our $vizinho = {};
$vizinho->{a} = 'b';
sub keyboard {
my ($meu_a, $meu_b) = #_;
return 4 if $meu_a eq $meu_b;
return 2 if $vizinho->{$meu_a} eq $meu_b;
return -1;
}
With the below line you create a scalar to a hash reference:
our $neighbor = {};
What I suspect you want is just a hash
our %neighbor = (
'foo' => {
'bar' => 1,
},
'biz' => {
'baz' => 1,
},
);
sub keyboard {
my ($key1, $key2) = #_;
return 4 if ($key1 eq $key2);
return 2 if $neighbor{$key1}{$key2}; #And use that $neighbor on that function
return -1;
}
Also note that $a and $b are special variables used by perl's sort function, so it's probably best to use another variable name for clarity.