mirod has asked for the wisdom of the Perl Monks concerning the following question:
OK, so I have had problems with stringing methods for a while.
See I often deal with trees, for example (completely not at random!): XML::Twig. And when I want to grab part of the tree near where I am I can either use a simili-XPath expression, but that's often a tad verbose, or just do $text= $elt->first_child( 'subelt')->first_child( 'subsubelt')->text. This works fine if both the subelt and the subsubelt elements exist. If either of then does not, then one of the first_child methods returns undef and Perl dies, letting me know that it does not like at all calling methods on undef. The problem is that most of the time I really don't care whether those elements exist or not. If there's something at the end of the string of method calls fine, otherwise I just want to get undef and get on with my... process.
As it is the only solution is to write:
$text= ( $elt->first_child( 'subelt') && $elt->first_child( 'sub_elt') +->first_child( 'subsubelt')) ? $elt->first_child( 'sub_elt')->first_child( 'subsubelt')->text : undef +;
Gross!
The ideal solution would be for Perl to return (if asked politely, through a pragma for example) undef for any method call on undef. Or to be able to bless undef. Pity you can't do that!
So here is the solution I found: instead of returning undef I return an "invalid object": an object blessed in a different package. This package has 4 methods:init is called by packages that want to use the feature, and gets a list of methods that will be "protected", new of course, which creates the object, a hasref which just holds the name of the class of the initial object (a blessed scalar would work actually, but lets not get into those!), is_valid which returns always undef, and AUTOLOAD, which returns the object if the method is protected or undef if it is not. This way I can still call methods on the previously undefined value returned.
This is not a perfect solution however. First it does not deal with inheritance, a class that inherits from foo still needs to call the init method with the list of foo methods to protect, yuck!. Then it is probably slow as it uses AUTOLOAD, even if the goal here is convenience, not efficiency. Also users of the objects cannot test whether the result is undefined anymore, they have to use a is_valid method.
Hence my question: can I do better? Can this method be improved to be more transparent, at least for the user? Is there an alternate method that works better? Has anyone already done something similar?
Here is the code:
#!/bin/perl -w use strict; package invalid; our $protected; # class1 => method_a => 1 # => method_b => 1 # class2 ... our $AUTOLOAD; # called by the package using the protection # methods to protect are stored for each class sub init { my( $class, $class_to_protect, @methods_to_protect)= @_; my %methods_to_protect=map { ($_, 1) } @methods_to_protect; $protected->{$class_to_protect} = \%methods_to_protect; } # an invalid object stores the cass of the original object sub new { my( $class, $obj)= @_; return bless { class => ref $obj}, $class; } # obviously it is not valid, that's the whole point! sub is_valid { return undef; } # return the invalid object or undef sub AUTOLOAD { my $invalid= shift; my $method= ( split /::/, $AUTOLOAD)[-1]; # get the method from $ +AUTOLOAD my $class= $invalid->{class}; # the original class fr +om the object if( $protected->{$class}->{$method}) { return $invalid; } # protected method else { return undef; } # unprotected one } package foo; # need to pass the list of methods to protect BEGIN { invalid->init( __PACKAGE__, qw( kid)); } sub new { my( $class, $value)= @_; return bless { value => $value}, $class; } sub add_kid { my( $self, $kid)= @_; $self->{kid}= $kid; } sub value { my $self= shift; return $self->{value}; } sub kid { my $self= shift; return $self->{kid} if( $self->{kid}); return invalid->new( $self); # do not return undef but a +n invalid object } sub is_valid { return $_[0]; } package main; my $parent= foo->new( 'parent'); my $kid= foo->new( 'kid'); $parent->add_kid( $kid); print "foo:\n"; print " parent: ", $parent->value || '', "\n"; print " parent->kid: ", $parent->kid->value || '', "\n"; print " parent->kid->kid: ", $parent->kid->kid->value || '', "\n"; print " parent->kid->kid->kid: ", $parent->kid->kid->kid->value || '' +, "\n\n";
|
|---|
| Replies are listed 'Best First'. | |
|---|---|
|
(tye)Re: Stringing method calls
by tye (Sage) on Oct 11, 2001 at 01:09 UTC | |
by tilly (Archbishop) on Oct 11, 2001 at 01:21 UTC | |
by tye (Sage) on Oct 11, 2001 at 01:28 UTC | |
|
Re (tilly) 1: Stringing method calls
by tilly (Archbishop) on Oct 11, 2001 at 01:13 UTC | |
|
Re: Stringing method calls
by pike (Monk) on Oct 11, 2001 at 15:00 UTC | |
by mirod (Canon) on Oct 11, 2001 at 15:44 UTC |