The following little idea seems so simple that I'm surprised that I haven't seen this before.

The following snippet may not work as you expect:

use Test::More tests => 3; + + use_ok('CGI') or die; can_ok('CGI', 'start_html'); ok(CGI->start_html, '... and calling it should succeed');

Running that produces the following output:

  1..3
  ok 1 - use CGI;
  not ok 2 - CGI->can('start_html')
  #     Failed test (test.pl at line 4)
  #     CGI->can('start_html') failed
  ok 3 - ... and calling it should succeed
  # Looks like you failed 1 tests of 3.

What gives? The test says that CGI.pm does not have a start_html() method, but calling that method succeeds. AUTOLOAD() can cause strange behaviors.

Or what about trying to validate the interface of delegated object methods? Just because I can stick any 'ol object in a slot doesn't mean that that object has the methods that I want, but if methods are AUTOLOADed, I can't easily validate them (I was also thinking about this in reference to traits validating required methods, but I don't know how best to implement that yet).

The solution that occurred to me was to provide stub methods:

package Foo; use stub qw'this that'; sub AUTOLOAD {...} # required

Then, when another object needs to validate the interface:

sub _delegate { # this is not a complete method. It's just here for illustratio +n my ($self, $slot, $object) { $self->{$slot}{object} = $object; my @missing; foreach my $method (@{$self->{$slot}{methods}}) { push @missing => $method unless UNIVERSAL::can($object, $met +hod); } die "Interface required @missing" if @missing; return $self; }

Now we can validate the interface even if the methods don't exist.

The actual stub package:

package stub; + + sub import { my @stub_methods = @_; my ($package) = caller; foreach my $stub (@stub_methods) { *{"$package::$stub"} = sub { goto &AUTOLOAD }; } } + + 1;

The package using stubs would be expected to have an AUTOLOAD method capable of handling all of the stub methods.

Did I miss anything? Is this even worth the trouble?

Cheers,
Ovid

New address of my CGI Course.

Replies are listed 'Best First'.
•Re: Creating stub methods
by merlyn (Sage) on Dec 29, 2003 at 17:58 UTC
    A more traditional practice is to provide "stub" subroutine declarations:
    sub foo; sub bar;
    At the moment, I can't remember exactly what problem this solves, but it should make them show up in can now. If you could figure out how this affects the symbol table and simulate that with your code (rather than a goto &AUTOLOAD), you might be closer to useful.

    -- Randal L. Schwartz, Perl hacker
    Be sure to read my standard disclaimer if this is a reply.

      At the moment, I can't remember exactly what problem this solves
      It solves the prototyping problem (%s() called too early to check prototype ).
Re: Creating stub methods
by PodMaster (Abbot) on Dec 29, 2003 at 18:03 UTC
    • perldoc subs
    • CGI should provide a can method
    • use before you try can :)
    update: Remember my vars::i, how about a subs::i, which would go something like
    =head1 NAME subs - Perl pragma to declare sub names and initialize them? =head1 SYNOPSIS use subs::i start_html => \&AUTOLOAD; =cut package subs::i; sub import { my $callpack = caller; my $pack = shift; my %imports = @_; foreach $sym (keys %imports) { *{"${callpack}::$sym"} = $imports{$sym}; } };

    MJD says "you can't just make shit up and expect the computer to know what you mean, retardo!"
    I run a Win32 PPM repository for perl 5.6.x and 5.8.x -- I take requests (README).
    ** The third rule of perl club is a statement of fact: pod is sexy.

      Actually, I think the "use subs" declaration solves the problem I was looking at, though I kind of like your "subs::i" idea. And you're right that it would be nice if CGI.pm provided a "can" method, but so many people use UNIVERSAL::can() directly that I think simply predeclaring the subs would be easier.

      Update: What I think is really nifty about the subs pragma is the line:

      *{"${callpack}::$sym"} = \&{"${callpack}::$sym"};

      We create the slot in the typeglob, but assign an undefined value. $module->can($method) works because we have the slot, but AUTOLOAD still gets called because there is no actual subroutine or method there. Nifty!

      Cheers,
      Ovid

      New address of my CGI Course.

        In fact it's not undefined in the full sense of the word. If you take a reference to a named sub which isn't yet defined, that reference magically becomes defined and valid as soon as that sub is defined. Observe:
        $_ = \&x; *x = sub { print "Oh my\n" }; $_->();
        This does print "Oh my". Perl is devious like that.

        Makeshifts last the longest.

Re: Creating stub methods
by liz (Monsignor) on Dec 29, 2003 at 18:42 UTC
    The test says that CGI.pm does not have a start_html() method, but calling that method succeeds. AUTOLOAD() can cause strange behaviors.

    Since the pod of UNIVERSAL.pm states:

    "$obj->can( METHOD )"
    "CLASS->can( METHOD )"
    "can( VAL, METHOD )"
    "can" checks if the object or class has a method called "METHOD". If it does then a reference to the sub is returned. If it does not then undef is returned. This includes methods inherited or imported by $obj, "CLASS", or "VAL".

    I have taken the approach of attempting to load the requested subroutine/method for the load module and returning the coderef if it could be loaded. Generally, if you test whether a sub/method exists, you'd also want to execute it. So it seemed the right thing to do.

    Liz

Re: Creating stub methods
by Anonymous Monk on Dec 29, 2003 at 18:45 UTC