http://qs1969.pair.com?node_id=25633

Hello! I often use some kind of expressions:
$self->{db}->{offer}->{product_id}->{name}
... or more longer. How I can easily and painfully detect if any of that are missing keys? I have this solution:
(($self->{db} || {})->{offer}||{})->{name}
..., but this is quite dirty. It is more complicated if I use tied and blessed references, because the empty hash ref. is not enough, because I often use methods on that: (See the TableMap module)
$self->{db}->{users}->{ $user_id_1 }->limits->{ $user_id_2 };
Is there a good and clean way to do this? I know that
eval{ $self->{$db}->{users}->{$user_id_1}->limits->{$user_id_2}; };
could be a good solution for trapping undefs in the middle of the hash-refs, but to tell the truth, I am waiting for a solution, that is more elegant. Idea?

Replies are listed 'Best First'.
RE: undefs and functions
by Russ (Deacon) on Aug 02, 2000 at 05:15 UTC
    Here's what I came up with... For each key in the structure, we provide the value we want for the default (if that key is missing). Then we work through the structure, replacing any undefined values with the desired default.

    I'm not at all sure this is the kind of solution you want, (and I'm not entirely certain it even works like I think it does) but it was a lot of fun to tinker with it. :-)

    use Data::Dumper; # Trying to guarantee this: #$self->{db}->{offer}->{product_id}->{name} # We start with a fully formed hashref $self->{db}->{offer}->{product_id}->{name} = 1; # and undef somewhere in the middle undef $self->{db}; # Show the structure just to prove it is too short print Dumper $self; my $Last = $self; for (['db',{}], ['offer',{}], ['product_id',&func], ['name',42]){ defined $Last->{$_->[0]} or $Last->{$_->[0]} = $_->[1]; $Last = $Last->{$_->[0]}; } print Dumper $self; # func() is the default for key 'product_id' above sub func{ {} }
    prints:
    $VAR1 = { 'db' => undef }; $VAR1 = { 'db' => { 'offer' => { 'product_id' => { 'name' => 42 } } } };

    Russ
    Brainbench 'Most Valuable Professional' for Perl

RE: undefs and functions
by dlux (Initiate) on Aug 02, 2000 at 11:05 UTC
    I think Ithe most elegant solution is the eval so far. (Thanks for the other suggestions!) But I think I will combine the results of the suggestions, and will come up with a new Class, which will be returned if an undefined or nonexistent hash key has been found. So I will do module like this:
    package TableMap::Undef; use strict; use overload "0+" => sub { return undef }, "bool" = sub { return undef }; sub new{ my $a={}; my $x=shift; bltess ($a,ref($x)||$x); tie(%$a,"TableMap::Undef::TIE"); return $a; }; sub AUTOLOAD { return new TableMap::Undef; }; sub DESTROY {}; package TableMap::Undef::TIE; use strict; sub TIEHASH { my $a={}; my $x=shift; bless ($a,ref($x)||$x); return $x; }; sub FETCH { return new TableMap::Undef; };
    Every time I need to return an undef I will return "new TableMap::Undef". This object is treated as undef as bool and numeric value, so this will cause right results when we test the value. This problem wuold be solved with a new perl pseudo-variable, we can call it $LAST for example, this is the value of the last similar level tag in the current expression. For example, I want to do things like this:
    my $x=$s->{db} && $LAST->{users} && $LAST->{$user_id_1} && $LAST->limi +ts && $LAST->{$user_id_2};
    What do you think about it? Will it be a good feature request in perl6?
RE: undefs and functions
by reptile (Monk) on Aug 02, 2000 at 03:42 UTC

    The following seems to have worked, but for calling on methods, you're probably still stuck (i didn't try, though, so it might work too). Just an example:

    my %foo = ( 'bar' => {'baz' => 1} ); print $foo{'baz'}->{'bar'} || 'oops';

    which prints 'oops' because there is no 'bar' in %foo.

    Update I misunderstood the question I think, but I tested this below and it still followed the same principle:

    my %foo = ( 'bar' => undef ); print $foo{'bar'}->{'baz'} || "oops";

    No warnings under -w or use strict.

    local $_ = "0A72656B636148206C72655020726568746F6E41207473754A"; while(s/..$//) { print chr(hex($&)) }

RE: undefs and functions
by perlmonkey (Hermit) on Aug 02, 2000 at 05:24 UTC
    I dont think there is an 'elegant' way to test each hash key in a nested hash. You could always come up with some funky loop and run exists on for each level. (my opinion would be that Russ's code is not 'elegant', however it is a solution, but there are probably dozens of solutions.)

    Given that, why do you even want to test each key value? Perl should not really care if it exists or not. Are you trying to prevent warnings that might be occuring, or do you not want the hash keys to be autovivified? The answer to this might help guide us in the right direction. But if you dont care about autovivification or 'Use of uninitialized value in ...' warnings I would say to not worry about the tests. They will only slow things down.
      I got error, not a simple warning, and that's the problem... "Cannot use an undefined value as a hash reference..."
        I maybe barking up the wrong tree, but I think I got your error. It took a bit of rooting around. Normally if the scalar is just a normal hash it should autovivify. Here is an autovivification example:
        my $foo = { }; $foo->{a}->{b}->{c}->{d} = 1; use Data::Dumper; print Data::Dumper->Dump([$foo],['foo']);
        Results:
        $foo = { 'a' => { 'b' => { 'c' => { 'd' => 1 } } } };
        All those keys were undef, but now they were created just by asking for them.

        I got your error only when I tried to turn an undef into a hash reference explicitly:print %{$foo->{a}->{d}} So maybe making it into a two step process:
        my $bar = $foo->{a}->{d}; print %$bar if $bar;
        that might take care of your errors. If I totally missed your problem, sorry.
RE: undefs and functions
by Anonymous Monk on Aug 02, 2000 at 19:50 UTC

    I often have the same problem with objetcs, stuff like: $elt->first_child->text in an XML tree, which crashes if $elt has no first child. In that case I would like to get just undef, but I still want to be able to test $elt->first_child and have it return a false value when there is no first_child.

    As a good monk the nicest solution would be to bless something false!

    A false value lessed into the appropriate object class would work just fine.

    Of course you can't do that! Traces of Larry's religious faith I guess...

    So the next solution I see is to create a new pragma, something like use default_undef_methods which would just default methods on undef objects to undef. I'll get to it as soon as I've got a minute!

    Would that solve your problem?