Aditya
Aditya

Reputation: 364

Given a number of variables to test for definedness, how to (easily) find out the one which was left undefined?

Today I saw this piece of code:

if (    not defined($reply_address) 
     or not defined($from_name) 
     or not defined($subject) 
     or not defined($date) )
{ 
  die "couldn’t glean the required information!"; 
}

(Jeffrey Friedl, "Mastering Regular Expressions", p. 59, 3rd ed.)

and I thought "How can I know which variable misfired?"

Of course, if there are only 4 variables to test, as in the example above, one could come up with:

if ( not defined $reply_address ) 
{ 
  die "\$reply_address is not defined" 
}
elsif ( not defined $from_name )
{
  die "\$from_name is not defined"
}
elsif ...

But what if there are 14 variables? Or 40...? One still needs to go through all of them, manually testing each and every one?

Isn't there a shorter, more "magical" way of telling which variable was left undefined?

Upvotes: 3

Views: 112

Answers (3)

Joe Casadonte
Joe Casadonte

Reputation: 16859

You can do what you want with symbolic references, though using them is generally not a great idea, and it can only be done with package variables, not lexically scoped variables (and lexically scoped variables are preferred to package variables -- see this answer for a brief comparison of the two).

#!/usr/bin/env perl

use strict;
use warnings;

use 5.014;

our($foo1) = 1;
our($bar1) = undef;
our($baz1) = 3;

foreach my $name (qw(foo1 bar1 baz1)) {
    {
        no strict 'refs';
        my($value) = $$name;
        warn "$name: is not defined" unless defined $value;
        say "$name: <$value>";
    }
}

Using warn instead of die for illustrative purposes.

</tmp> $ ./test.pl
foo1: <1>
bar1: is not defined at ./test.pl line 16.
Use of uninitialized value $value in concatenation (.) or string at ./test.pl line 17.
bar1: <>
baz1: <3>

You can also just loop through all of the variables using common code to check them:

#!/usr/bin/env perl

use strict;
use warnings;

use 5.014;

my($foo2) = 1;
my($bar2) = undef;
my($baz2) = 3;

foreach my $vardef (["foo2", $foo2], ["bar2", $bar2], ["baz2", $baz2]) {
    my($name) = $vardef->[0];
    my($value)  = $vardef->[1];

    warn "$name: is not defined" unless defined $value;
    say "$name: <$value>";
}

which gives similar output:

foo2: <1>
bar2: is not defined at ./test.pl line 29.
Use of uninitialized value $value in concatenation (.) or string at ./test.pl line 30.
bar2: <>
baz2: <3>

Finally, if you can manage to get the variables into a hash, you can loop through the keys of the hash and test them that way:

#!/usr/bin/env perl

use strict;
use warnings;

use 5.014;

my($vars) = {
    foo3 => 1,
    bar3 => undef,
    baz3 => 3,
};

foreach my $name (sort keys %$vars) {
    my($value)  = $vars->{$name};

    warn "$name: is not defined" unless defined $value;
    say "$name: <$value>";
}

I threw the sort in there because I like deterministic behavior...

bar3: is not defined at ./test.pl line 42.
Use of uninitialized value $value in concatenation (.) or string at ./test.pl line 43.
bar3: <>
baz3: <3>
foo3: <1>

If the test really was as simple as die if ! defined then I would probably just list them out:

#!/usr/bin/env perl

use strict;
use warnings;

use 5.014;

my($foo4) = 1;
my($bar4) = undef;
my($baz4) = 3;

die qq([ERROR] \$foo4 not defined\n) unless defined $foo4;
die qq([ERROR] \$bar4 not defined\n) unless defined $bar4;
die qq([ERROR] \$baz4 not defined\n) unless defined $baz4;

which just gives us:

[ERROR] $bar4 not defined

The last approach is just very straightforward and unambiguous. If the test is not as dead simple as this, then I'd go with the second approach. If you're worried about a list of 40 (or even 14) checks of this nature, then I'd look at the design.

See also this PadWalker code example for a very complicated version of the first option, but allowing lexically scoped variables.

Upvotes: 3

jo-37
jo-37

Reputation: 91

Could be done with a string-eval:

use strict;
use warnings;

my ($reply_address, $from_name, $subject, $date) = ('', '', undef, '');

for my $var (qw(reply_address from_name subject date)) {
    my $defined;
    eval "\$defined = defined \$$var";
    die "eval failed: $@" if $@;
    die "\$$var is not defined" unless $defined;
}

Upvotes: 1

H&#229;kon H&#230;gland
H&#229;kon H&#230;gland

Reputation: 40738

You could create a table to simplify a little bit:

use strict;
use warnings;

my $reply_address = "xyz";
my $from_name;
my $subject = "test";
my $date;

my @checks = (
    [\$reply_address, '$reply_adress'],
    [\$from_name, '$from_name'],
    [\$subject, '$subject'],
    [\$date, '$date'],
);

for my $check (@checks) {
    if (not defined ${$check->[0]}) {
        die $check->[1] . " is not defined";
    }
}

Upvotes: 6

Related Questions