In Perl, is there graceful way to convert undef to 0 manually?
Asked Answered
S

5

8

I have a fragment in this form:

my $a = $some_href->{$code}{'A'}; # a number or undef
my $b = $some_href->{$code}{'B'}; # a number or undef
$a = 0 unless defined($a);
$b = 0 unless defined($b);
my $total = $a + $b;

The reality is even more messy, since more than two variables are concerned.

What I really want to write is this:

my $total = $some_href->{$code}{'A'} + $some_href->{$code}{'B'};

and have undef correctly evaluate to 0 but I get these warnings in almost every run:

Use of uninitialized value in addition (+) at Stats.pm line 192.

What's the best way to make these messages go away?

NB: I 'use strict' and 'use warnings' if that s relevant.

Stutter answered 29/8, 2009 at 10:37 Comment(1)
It is relevant. In this case, you've enabled a warning that you don't care about.Deacon
V
17

It's good that you're using strict and warnings. The purpose of warnings is to alert you when Perl sees behavior that's likely to be unintentional (and thus incorrect). When you're doing it deliberately, it's perfectly fine to disable the warning locally. undef is treated as 0 in numeric contexts. If you're okay with both having undefined values and having them evaluate to zero, just disable the warning:

my $total;
{
  no warnings 'uninitialized';
  $total = $some_href->{$code}{A} + $some_href->{$code}{B};
}

Note: Disable only the warnings you need to, and do so in the smallest scope possible.

If you're averse to disabling warnings, there are other options. As of Perl 5.10 you can use the // (defined-or) operator to set default values. Prior to that people often use the || (logical-or), but that can do the Wrong Thing for values that evaluate to false. The robust way to default values in pre-5.10 versions of Perl is to check if they're defined.

$x = $y // 42;             # 5.10+
$x = $y || 42;             # < 5.10 (fragile)
$x = defined $y ? $y : 42; # < 5.10 (robust)
Vitreous answered 29/8, 2009 at 14:3 Comment(1)
Yes, "$y || 42" is fragile, but "$y || 0" is not quite as fragile.Dochandorrach
S
6

You can turn off the “uninitialized” warning for a second:

my $a;
my $b = 1;
{
    no warnings 'uninitialized';
    my $c = $a+$b; # no warning
}
my $c = $a+$b; # warning

Or you can short-circuit to zero:

my $d = ($a||0)+$b; # no warning

Doesn’t look very nice to me though.

Stapes answered 29/8, 2009 at 10:45 Comment(0)
P
5
my $a = $some_href->{$code}{'A'} || 0;
my $b = $some_href->{$code}{'B'} || 0;
my $total = $a + $b;

In this case, it's OK to treat false values the same as undefined values because of your fallback value.

Padus answered 29/8, 2009 at 13:48 Comment(2)
This code doesn't quite do the same thing. It also turns the empty string, a defined value, into 0. That may not be what you want.Hanky
I assumed, as he was adding them, it was in fact what he wanted.Padus
T
4

As you are adding them, just filter out the undefs.

use List::Util 'sum';

my $total = sum (0, grep {defined} $some_href->{$code}{'A'}, $some_href->{$code}{'B'});

Or even

use List::Util 'sum';

my $total = sum (0, grep {defined} map {$some_href->{$code}{$_}} 'A', 'B');
Tymon answered 29/8, 2009 at 11:11 Comment(0)
A
0

To convert undef to 0, use either of $var // 0 or $var //= 0. The operator is somewhat new (couldn't find when it was introduced), but practically any Perl in use should have it now.

Acedia answered 23/5, 2022 at 9:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.