in reply to Re^2: Autoloading tie routines
in thread Autoloading tie routines

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.

Replies are listed 'Best First'.
Re^4: Autoloading tie routines
by cmac (Monk) on Feb 03, 2009 at 06:27 UTC
    Based on the the statement "the AUTOLOAD sub is called with the arguments that the unknown sub would be called with", here is my AUTOLOAD in ABC.pm.
    # 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 { my $constname; ($constname = $AUTOLOAD) =~ s/.*:://; my $val = constant($constname, 0); if ($!) { # the name in $AUTOLOAD is not a constant defined by XYZ::ABC if (my ($kind, $nolock, $function) = $AUTOLOAD =~ /^XYZ::ABC::(Scalar|Array|Hash)::(NoLock::)?( +[A-Z]+)$/) { if ($function =~/^TIE$kind$/i) { my $self = shift; my $sah = shift; # sah = scalar/array/hash return bless \$sah, $self; # vv Scalar or Array or Hash } elsif ($function =~ /^(FETCH|STORE)$/ || $kind !~ /^S/ # vv Array or Hash && $function =~ /^(DELETE|EXISTS|CLEAR)$/ || $kind =~ /^A/ # vv Array && $function =~ /^(FETCHSIZE|STORESIZE|EXTEND|POP|PU +SH|SHIFT|UNSHIFT|SPLICE)$/ || $kind =~ /^H/ # vv Hash && $function =~ /^(FIRSTKEY|NEXTKEY|SCALAR)$/) { my $subname = 'abc' . ($nolock ? 'a_' : '_') . lc($kin +d) . '_' . lc($function); my $sah_ref = shift; { no strict 'refs'; # differentiate subs with and without return value +s if ($function =~ /^(ST|CL|PU|UN|EXT)/) { &$subname ($$sah_ref, @_); return; } else { return &$subname ($$sah_ref, @_); } } } elsif ($function =~ /^(UNTIE|DESTROY)$/) { # how do we do nothing? return; # ?? } } 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 eval "sub $AUTOLOAD { $val }"; # can this be $constname rather tha +n $AUTOLOAD? goto &$AUTOLOAD; # can this just be 'return' if all of the defined + names really are constants? }
    The routines that implement most of the 'tie' calls already exist in ABC.xs, with the same names (lowercased). It pleases me very much to get rid of 52 silly wrapper routines.

    At the end of ABC.pm are six packages like this:
    package XYZ::ABC::Scalar; use XYZ::ABC qw(AUTOLOAD); our @ISA = qw(XYZ::ABC); # are this and the use both needed? 1;
    Suggestions and corrections for AUTOLOAD and these packages are very welcome. In its first test AUTOLOAD allowed perl to do a tie statement to XYZ::ABC::Scalar.

    Thanks for being there,
    cmac
    www.animalhead.com
      Are you aware of everything that the AUTOLOAD has done with those 6 packages? Every constant in ABC, every method in ABC, all are inherited by XYZ::ABC::Scalar. Is this really what you want? Furthermore ABC looks like an example of the God module anti-pattern. That is a code smell right there.

      Finally you are obviously confused about how this works because you don't know whether you need to both import AUTOLOAD and inherit. The answer is that if you don't have an import method in ABC.pm, then the use statement does nothing and should be removed. If you have an import that will export AUTOLOAD, then the AUTOLOAD written for ABC.pm will fail to work in another package because it will be looking at $ABC::AUTOLOAD for the requested function, but Perl will put it in $XYZ::ABC::Scalar::AUTOLOAD instead and you won't find it properly. Therefore the use should definitely be removed.

      A much cleaner solution is at the end to have something that looks like this:

      # Presumably you have 6 packages listed in this list for my $package (qw( XYZ::ABC::Scalar )) { $package =~ /(Scalar|Array|Hash)(::NoLock)?/ or die "Tie type of '$package' not understood"; my $kind = $1; my $nolock = $2; no strict 'refs'; *{$package . uc("::TIE$kind")} = sub { my $class = shift; my $ssh = shift; # Scalar/Hash/Array return bless $ssh, $class; }; # These two do nothing. *{"$package\::DESTROY"} = sub {}; *{"$package\::UNTIE"} = sub {}; # And now the rest. my @functions = qw(FETCH STORE); if ($kind eq "Array") { push @functions, qw( FETCHSIZE STORESIZE EXTEND CLEAR POP PUSH SHIFT UNSHIFT SPLICE ); } elsif ($kind eq "Hash") { push @functions, qw( STORE DELETE CLEAR EXISTS FIRSTKEY NEXTKEY SCALAR ); } for my $function (@functions) { my $subname = join "_", ($nolock ? "abca" : "abc"), lc($kind) +. lc($function); *{"$package\::$function"} = \&$subname; } }
      This will create the 6 packages, set all of the right methods, not create any unwanted additional methods, and gets rid of the overhead of running the AUTOLOAD every time you want to access one of the tied methods. Also by moving logic out of the AUTOLOAD you make the logic that remains in the AUTOLOAD clearer. Plus if anyone has code that uses the can method for introspection, it will work on those packages.

      The moral? AUTOLOAD is a big sledgehammer. Don't swing it unless you need to, and then swing it carefully.

        Please explain "Furthermore ABC looks like an example of the God module anti-pattern. That is a code smell right there." Colorful language!

        Obviously there are is a lot of stuff in XYZ::ABC, but I'm working toward a successor module to an existing CPAN module, which has the same Scalar, Hash, and BTree stuff. I admit to making it worse by adding the Array stuff. But hopefully the linker will treat the ABC.so file as a library, and only load what's called for.

        "everything that the AUTOLOAD has done with those 6 packages"? (There are now 8 because I added BTree as an alias for Hash, to be more compatible with the predecessor module.) Far as I know, AUTOLOAD has done nothing but be there to field attempts to use unknown constants and subroutines. The subsidiary package names cannot be use'd, and exist only to be used in tie statements.

        There is no "use AutoLoad" in ABC.pm any more. ABC.pm's AUTOLOAD is free-standing, and passes the tie calls to subs in ABC.xs that are also available as direct calls to users of XYZ::ABC.

        Rather than define 82 names in the symbol table at the start of execution, the compromise course between your routine and the direct-through AUTOLOAD that for the moment is still shown below, is for AUTOLOAD to put each name for which it is called in the symbol table (thus eliminating future calls to AUTOLOAD for this name) and then go to the newly defined sub. It will need to make an anonymous sub that dereferences the first operand from perl to match what the destination XS routine expects.

        If I wasn't "confused about how this works" I wouldn't be posting to perlmonks. ABC.pm does not have an import method. It has an export method that used to include AUTOLOAD in @EXPORT_OK. But I took the use's out of the subsidiary packages, and no longer export AUTOLOAD. Thank you for this!

        We must be careful about commenting about the clarity of each others' code. I find your routine above quite readable, until I come to *name = thing; which I have not used. Based on your code I made several improvements to my AUTOLOAD routine, which looks like this PENDING THE "STORE THE NAME" IMPROVEMENT NOTED ABOVE:
        # 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 eq 'BTree') {$sah = 'Hash'} if ($function eq uc("TIE$sah")) { my $self = shift; my $base_sah = shift; # sah = scalar/array/hash 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/; my $subname = lc($abcx) . '_' . lc($sah) . '_' . lc($f +unction); my $base_sah_ref = shift; unshift @_, $$base_sah_ref; # dereference the base sca +lar/array/hash goto &$subname; } 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 eval "sub $AUTOLOAD { $val }"; # can this be $constname rather tha +n $AUTOLOAD? goto &$AUTOLOAD; # can this just be 'return' if all of the defined + names really are constants? }
        I would appreciate comments on the last two questions in its comments.

        Thanks for being there,
        cmac
        www.animalhead.com