Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl-Sensitive Sunglasses
 
PerlMonks  

Autoloading tie routines

by cmac (Monk)
on Feb 02, 2009 at 17:24 UTC ( [id://740752]=perlquestion: print w/replies, xml ) Need Help??

cmac has asked for the wisdom of the Perl Monks concerning the following question:

This got buried so I'm putting it up again at the top level.

I have a problem with a naming structure inherited from the guy who wrote a package that I'm working to supersede. Say the module/package is named XYZ::ABC. Using this package, people want to tie to names like XYZ::ABC::Scalar. The way he did this was to write out the names of the tie routines in full in ABC.pm, like this:
# Preloaded methods go here. sub XYZ::ABC::Scalar::TIESCALAR { my $class = shift; my ($val) = @_; return bless \$val, $class; }
This works in preloaded form, but it seems it can't be autoloaded.

What do can I do to keep the inherited naming structure and autoload the tie routines? Can I put multiple package statements/sections in my one ABC.pm file?

If each package needs a separate .pm file, the changes to my Makefile.PM will be extensive. Do I recall a bit in the ExtUtils::MakeMaker docs about handling multiple things in one Makefile.PM?

In all there are 6 "tie-to" names and 52 "tie interface" routines. Even though each one has a pretty simple structure like:
sub XYZ::ABC::Array::STORE { my $self = shift; abc_array_store ($self->{ARRAY}, @_); }
52 of them make autoloading seem worthwhile. Although if they are in separate packages, each one containing only the tie routines for one "tie-to" name, maybe autoloading isn't worthwhile?

Thanks to any monk who can help,
cmac
www.animalhead.com

Replies are listed 'Best First'.
Re: Autoloading tie routines
by tilly (Archbishop) on Feb 02, 2009 at 20:09 UTC
    Piece of advice. If you know the names of all of the routines, and they are repetitive enough to AUTOLOAD, why not create them in advance by assigning closures to the symbol table? That is rather than something like this:
    sub AUTOLOAD { my @name_pieces = split /::/, $AUTOLOAD; my $function = pop @name_pieces; my $package = join "::", @name_pieces; no strict 'refs'; *{"$package\::$function"} = get_sub($package, $function); goto &{"$package\::$function"}; }
    You write something like this:
    for my $package (@packages) { for my $function (@functions) { no strict 'refs'; *{"$package\::$function"} = get_sub($package, $function); } }
    and now there is no need to write an AUTOLOAD. Which means that chromatic doesn't complain that can doesn't do the right thing, your AUTOLOAD doesn't break when it sees functions you didn't mean to catch, you don't blow the method cache a bunch of times, and life is generally better.
      Tilly:
      Thers is an AUTOLOAD in ABC.pm that loads constants using the constant routine in ABC.xs. ABC.pm exports the direct-call sub names (for those who don't want to use the tied interface) and the constants. Here is the AUTOLOAD routine in ABC.pm:
      our $AUTOLOAD; sub AUTOLOAD { # this is used to 'autoload' constants from the constant() # XS function. If a constant is not found then control is passed # to the AUTOLOAD in AutoLoader. my $constname; ($constname = $AUTOLOAD) =~ s/.*:://; croak "& not defined" if $constname eq 'constant'; my $val = constant($constname, @_ ? $_[0] : 0); if ($! != 0) { if ($! =~ /Invalid/ || $!{EINVAL}) { $AutoLoader::AUTOLOAD = $AUTOLOAD; goto &AutoLoader::AUTOLOAD; } else { croak "Your vendor has not defined $constname"; } } eval "sub $AUTOLOAD { $val }"; goto &$AUTOLOAD; }
      Your solution is not clear to me. Does it provide autoloading, or is it them same as putting all of the tie routines as preloaded (before the __END__)? Can you please re-cast it for use with the existing AUTOLOAD?
        This would be an example of why moving more and more dispatch logic into AUTOLOAD leads to problems.

        You have a complex AUTOLOAD that already does two very different things. It defines a series of constants. And you have a set of functions that are only loaded if they are used. Unless you've written out all of the tie methods and AutoSplit them for AutoLoader to find, you haven't implemented a tie interface. So making this do that is not just a question of making a small modification to this AUTOLOAD routine.

        Making things worse, ABC is not inherited by the classes that you want to be able to tie to. So if you want to use AUTOLOAD, you have to write a new AUTOLOAD that is in that class, or in a superclass that is loaded from that class. Which lead to your original question that you didn't know how to write said AUTOLOAD.

        So show me how to write the AUTOLOAD you want to write, and I'll show you how to convert it. Otherwise look at the template I wrote, and try to write get_sub which will, for instance, take "XYZ::ABC::Array" and "STORE" as arguments, then returns an anonymous subroutine.

Re: Autoloading tie routines
by kennethk (Abbot) on Feb 02, 2009 at 18:14 UTC

    What do can I do to keep the inherited naming structure and autoload the tie routines? Can I put multiple package statements/sections in my one ABC.pm file?

    Yes, though it can get a little messy. For example, you could call this module

    package Foo; use strict; use warnings; package Foo::Bar; use strict; use warnings; sub foo { print 'foo'; } return 1;

    with the script

    use Foo; use strict; use warnings; Foo::Bar->foo;

    Note that you cannot use Foo::Bar since the file does not exist. The Foo use statement loads Foo::Bar into the namespace. (-- Not quite true: see below)

    52 of them make autoloading seem worthwhile. Although if they are in separate packages, each one containing only the tie routines for one "tie-to" name, maybe autoloading isn't worthwhile?

    You can use a base class (i.e. ISA) to define your autoload routines, so no matter how many files you have, the actual code is localized. A good intro (if you need it) on constructing a base class for exactly this is found in perltoot.

    Update: As a template for the autoload code, I give you a modified module

    package Foo; use strict; use warnings; our $AUTOLOAD; sub AUTOLOAD { my $self = shift; my $class = ref($self) || $self; my $name = $AUTOLOAD; print "$name in $class\n"; } package Foo::Bar; use strict; use warnings; use Exporter; our @ISA = qw(Foo); sub foo { print "foo\n"; } return 1;

    and script

    use Foo; use strict; use warnings; Foo::Bar->foo; Foo->bar; Foo::Bar->bar;

    Now if you'll excuse me, I have some documentation to read -- ikegami just blew my mind.

      Note that you cannot use Foo::Bar since the file does not exist. The Foo use statement loads Foo::Bar into the namespace.

      There's a way of using use to load (add a code ref to @INC), and there's a way of using use to import (add the module to %INC).

      If appropriate, please update your post when your mind has recovered from Ikegami's inscrutable note :-)
Re: Autoloading tie routines
by cmac (Monk) on Feb 04, 2009 at 02:47 UTC
    For cloture of this thread (not to be confused with "foreclosure" [a popular topic these days] nor with "closure" in perl and other languages [consult tilly about this]), here is the AUTOLOAD routine I ended up with:
    # AUTOLOAD is used to # 1) 'autoload' constants from the constant() function in ABC.xs # If the name is not a constant then it's parsed for # 2) a tie package-name::function-name, which if matched is executed our $AUTOLOAD; # implicit argument of AUTOLOAD sub AUTOLOAD { # make the base name (without the "package::") (my $constname = $AUTOLOAD) =~ s/.*:://; # call the constant lookup routine in ABC.xs my $val = constant($constname, 0); if ($!) { # the name in $AUTOLOAD is not a constant defined by XYZ::ABC # sah = scalar/array/hash if (my ($abcx, $sah, $function) = $AUTOLOAD =~ /^XYZ::(ABCA?)::(Scalar|Array|Hash|BTree)::([ +A-Z]+)$/) { if (($sah = lc $sah) eq 'btree') {$sah = 'hash'} if ($function eq uc("TIE$sah")) { my $self = shift; my $base_sah = shift; # sah = scalar/array/hash $val = ''; if (!$base_sah || ($val = ref($base_sah)) ne "abc_${sa +h}Ptr") { croak "3rd operand of tie should be the return val +ue from abc_make_$sah: ref was '$val'"; } return bless \$base_sah, $self; # Scalar or Array or Hash } elsif ($function eq 'FETCH' || $function eq 'STORE' || $sah ne 'scalar' # Array or Hash && $function =~ /^(DELETE|EXISTS|CLEAR)$/ || $sah eq 'array' && $function =~ /^(FETCHSIZE|STORESIZE|EXTEND|POP|PU +SH|SHIFT|UNSHIFT|SPLICE)$/ || $sah eq 'hash' && $function =~ /^(FIRSTKEY|NEXTKEY|SCALAR)$/) { $function =~ s/KEY$/_KEY/; if ($sah eq 'array' #nowrap && $function =~ /^(FETCH$|STORE$|EXI|D|SP)/) {#nowrap $function .= '_nowrap'; #nowrap } my $subname = lc($abcx) . "_${sah}_" . lc($function); no strict 'refs'; # define the symbol so AUTOLOAD won't be called again +for this name *$AUTOLOAD = sub { # dereference the base scalar/array/hash my $base_sah_ref = shift; unshift @_, $$base_sah_ref; # go to the constructed sub name in ABC.xs goto &$subname; }; # having defined the symbol, execute the sub in ABC.xs goto &$AUTOLOAD; } elsif ($function eq 'UNTIE' || $function eq 'DESTROY') { return; # do nothing } } croak "$AUTOLOAD is not a defined constant or subroutine for X +YZ::ABC"; } # the name in $AUTOLOAD is a constant defined by XYZ::ABC: define +it for perl no strict 'refs'; # define the symbol so AUTOLOAD won't be called again for this nam +e *$AUTOLOAD = sub{$val}; # in the general case the following line should be goto &$AUTOLOAD +; return $val; }
    If it changes I'll update this posting...

    Thanks to all esp. tilly,
    cmac
    www.animalhead.com

    A "universal can" is one that anyone can kick, drink from, or urinate into, although certain interactions among these methods are to be avoided...

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://740752]
Approved by Corion
Front-paged by jdporter
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others chilling in the Monastery: (4)
As of 2024-04-25 07:00 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found