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

    For quite a while I've had writing just such a beast on my to-do list. My plan was to overload the object so that any use other than calling a method on it returned undef.

    Along similar lines, I'd like it to be easy to write code where, if you don't bother to check for success, then the module dies for you. But if you do bother to check for success, then the module returns a failure code and doesn't die.

    To combine both of the these conveniences means that I need the "invalid object" to die if it gets destroyed before undef was ever returned (if that option has been turned on, which would probably be the default).

    If you flesh this out, please let me know so that I can use it. (:

    (So part of the plan is for the "invalid object" to record which method failed on which object along with a nice, detailed explanation as to why so there'd probably be one or two special methods on the "invalid object" that allowed you to retrieve these details as well -- and these methods would also disarm the auto-die feature.)

            - tye (but my friends call me "Tye")
      For the second problem I would just write a small function:
      sub error_out { defined(wantarray()) ? shift : croak("Untrapped error '$_[0]'"); }
      and then in your module generously use code like:
      return error_out($failure) if $failure;

        No kidding; that part isn't hard. I only mentioned it because I want to combine the two features and the obvious method you show above doesn't help in that case.

                - tye (but my friends call me "Tye")
Re (tilly) 1: Stringing method calls
by tilly (Archbishop) on Oct 11, 2001 at 01:13 UTC
    This isn't very pretty, but first define a class:
    package Undef; sub AUTOLOAD { undef; }
    and then you can string at the cost of a few parens with:
    $text = (( $elt->first_child( 'subelt') || 'Undef' )->first_child( 'subsubelt')||'Undef' )->text();
    Don't like that? Well then you can just create a method to do the calls dynamically like this:
    # Takes an array of anon arrays of method calls, and # chains them. Returns the first non-object of the end of # the call. sub chain_meth { my $obj = shift; my $last_call = pop; foreach my $call (@_) { my ($meth, @args) = @$call; $obj = $obj->$meth(@args); return $obj unless ref($obj); } # Make the last one respect context properly. my ($meth, @args) = @$last_call; return $obj->$meth(@args); }
    and now you can:
    $text = $elt->chain_meth( ["first_child", "subelt"], ["first_child", "subsubelt"], ["text"] );
Re: Stringing method calls
by pike (Monk) on Oct 11, 2001 at 15:00 UTC
    Can't you just put an  eval block around the statement? Should avoid dying and leave you with an undef'd  $text if one of the subnodes doesn't exist:

    my $text; eval{$elt->first_child( 'sub_elt')->first_child( 'subsubelt')->text;}

    Or did I miss something?

    pike

      I certainly could. The problem is that I am not the only one (I hope!) using the module, and I want to make it as easy as possible for people who are not familiar with eval to use the module.

      Maybe after all I should just add this to the documentation, along with something restating the eval { BLOCK } is _not_ slow (it seems that a lot of people avoid eval systematically on account of it being slow, when only the eval "string" version can be slower if included in a loop for example).