tags:

views:

85

answers:

4

Is there a module that provides functionality like Template Toolkit does when accessing a deeply nested data structure? I want to pull out something like $a = $hash{first}[0]{second}{third}[3] without having to test each part of the structure to see if it conforms to what I expect. If %hash = {} I want $a = undef, not produce an error.

A: 

Something like this?

use strict;
use warnings;

my %hash;
my $elem = _eval( '$hash{first}[0]{second}{third}[3]' );

sub _eval {return (eval shift) // undef}

Of course you might as well do:

my $elem = eval {$hash{first}[0]{second}{third}[3] // undef};
Pedro Silva
Why do you like silently turning all zeroes and empty strings into undef? ;)
hobbs
Heh, you're right. Edited post...
Pedro Silva
I'm not sure what the point of including `... // undef` is. If the expression prior to `//` is evaluated to anything but undef, that value will be used, if it is evaluated to undef you don't need to explicitly use undef as the default.
Ven'Tatsu
The point is to show how to default to a particular value, `undef` being the actual one that was requested.
Pedro Silva
+2  A: 

Perl will do exactly what you described

This feature is called autovivification. Which means that container objects will spring into existence as soon as you use them. This holds as long as you don't violate any precedent you set yourself.

For example, trying to dereference something as a hash when you have already used it as an array reference is an error. More generally, if the value is defined, it can only be dereferenced as a particular type if it contains a reference to that type.

If you want protection against misuse as well, you can wrap the nested lookup in an eval block:

my $x = eval{ $hash{first}[0]{second}{third}[3] };

This will return undef if the eval fails. Note that this is NOT a string eval, which would be written eval '....';. In block form, Perl's eval is like the try {...} construct in other languages.

To determine if the eval failed or if the value in that position really is undef, test to see if the special variable $@ is true. If so, the eval failed, and the reason will be in $@. That would be written:

my $x = eval{ $hash{first}[0]{second}{third}[3] };

if (!$x and $@) { die "nested dereference failed: $@" }

Or you can use the module Try::Tiny which abstracts away the implementation details and protects against a few edge cases:

use Try::Tiny;

my $x;
try {
    $x = $hash{first}[0]{second}{third}[3];
} catch {
    die "nested dereference failed: $_";
};
Eric Strom
+1  A: 

Your error likely comes from wrong level of indirection, not because you don't have a value. Note that your hash variable is a scalar reference to hash, not a hash. So it should be defined as $hash = {}, not %hash = {}. Then, you access the elements there as $hash->{first}, not $hash{first}. And so on. If you define hash properly and try something like $hash->{first}->[0]->{second}->{third}->[3], you will get exactly undef, as you wanted, no errors.

Note: always use strict!

Igor Krivokon
+1  A: 

Check out Data::Diver.

You can access an arbitrary nested structure by key name (it doesn't matter if a layer is a hash or array). The Dive() subroutine will return an empty list if there is an error or it will return a matching value.

use strict;
use warnings;

use Data::Diver qw( Dive );

my $a = Dive( \%hash, 'first', 0, 'second', 'third', 3 );

if( defined $a ) {
    print "Got '$a'.\n";
}
else {
    print "Got no match.\n";
}
daotoad