Class-Multimethods

 view release on metacpan or  search on metacpan

lib/Class/Multimethods.pm  view on Meta::CPAN

package Class::Multimethods;

use strict;
use vars qw($VERSION @ISA @EXPORT);
use Carp;

our $VERSION = '1.701';

require Exporter;
@ISA = qw(Exporter);
@EXPORT = qw( multimethod resolve_ambiguous resolve_no_match superclass multimethod_wrapper );

use vars qw(%dispatch %cached %hasgeneric %ambiguous_handler %no_match_handler %max_args %min_args %dispatch_installed);

%dispatch = ();                                         # THE DISPATCH TABLE
%cached   = ();                                         # THE CACHE OF PREVIOUS RESOLUTIONS OF EMPTY SLOTS
%hasgeneric  = ();          # WHETHER A GIVEN MULTIMETHOD HAS ANY GENERIC VARIANTS
%ambiguous_handler = ();  # HANDLERS FOR AMBIGUOUS CALLS
%no_match_handler = ();   # HANDLERS FOR AMBIGUOUS CALLS
%max_args = ();                     # RECORDS MAX NUM OF ARGS IN ANY VARIANT
%min_args = ();             # RECORDS MIN NUM OF ARGS IN ANY VARIANT

%dispatch_installed = (); # RECORDS DISPATCHES ALREADY INSTALLED __BY__ __US__

# THIS IS INTERPOSED BETWEEN THE CALLING PACKAGE AND Exporter TO SUPPORT THE
# use Class:Multimethods @methodnames SYNTAX

sub import
{
    my $package = (caller)[0];
    install_dispatch($package,pop @_) while $#_;
    Class::Multimethods->export_to_level(1);
}


# INSTALL A DISPATCHING SUB FOR THE NAMED MULTIMETHOD IN THE CALLING PACKAGE

sub install_dispatch
{
    my ($pkg, $name) = @_;
    # eval "sub ${pkg}::$name { Class::Multimethods::dispatch('$name',\@_) }"
    if ( ! $dispatch_installed{$pkg}{$name} )
    {
            eval(make_dispatch($pkg,$name)) || croak "internal error: $@";
            $dispatch_installed{$pkg}{$name}= 1;
    }
    #eval(make_dispatch($pkg,$name)) || croak "internal error: $@"
    #   unless eval "defined \&${pkg}::$name";
}

# REGISTER RESOLUTION FUNCTIONS FOR AMBIGUOUS AND NO-MATCH CALLS

sub resolve_ambiguous
{
    my $name = shift;
    if (@_ == 1 && ref($_[0]) eq 'CODE')
        { $ambiguous_handler{$name} = $_[0] }
    else
        { $ambiguous_handler{$name} = join ',', @_ }
}

sub resolve_no_match
{
    my $name = shift;
    if (@_ == 1 && ref($_[0]) eq 'CODE')
        { $no_match_handler{$name} = $_[0] }
    else
        { $no_match_handler{$name} = join ',', @_ }
}

# GENERATE A SPECIAL PROXY OBJECT TO INDICATE THAT THE ANCESTOR OF AN OBJECT'S
# CLASS IS REQUIRED

sub superclass
{
    my ($obj, $super) = @_;
    $super = ref($obj) || ( (~$obj&$obj) eq 0 ? '#' : '$' ) if @_ <= 1;
    bless \$obj, (@_ > 1 )
        ? "Class::Multimethods::SUPERCLASS_IS::$super"
        : "Class::Multimethods::SUPERCLASS_OF::$super";
}

sub _prettify
{
                $_[0] =~ s/Class::Multimethods::SUPERCLASS_IS:://
    or $_[0] =~ s/Class::Multimethods::SUPERCLASS_OF::(.*)/superclass($1)/;
}

# SQUIRREL AWAY THE PROFFERED SUB REF INDEXED BY THE MULTIMETHOD NAME
# AND THE TYPE NAMES SUPPLIED. CAN ALSO BE USED WITH JUST THE MULTIMETHOD
# NAME IN ORDER TO INSTALL A SUITABLE DISPATCH SUB INTO THE CALLING PACKAGE

sub multimethod
{
    my $package = (caller)[0];
    my $name  = shift;
    install_dispatch($package,$name);

    if (@_)         # NOT JUST INSTALLING A DISPATCH SUB...
    {
        my $code = pop;
        croak "multimethod: last arg must be a code reference"
            unless ref($code) eq 'CODE';

        my @types = @_;

        for ($Class::Multimethods::max_args{$name})
            { $_ = @types if !defined || @types > $_ }
        for ($Class::Multimethods::min_args{$name})
            { $_ = @types if !defined || @types < $_ }
            
        my $sig = join ',', @types;

        $Class::Multimethods::hasgeneric{$name} ||= $sig =~ /\*/;

        carp "Multimethod $name($sig) redefined"
            if $^W && exists $dispatch{$name}{$sig};
        $dispatch{$name}{$sig} = $code;

        # NOTE: ADDING A MULTIMETHOD COMPROMISES CACHING
        # THIS IS A DUMB, BUT FAST, FIX...
        $cached{$name} = {};
    }
}


# THIS IS THE ACTUAL MEAT OF THE PACKAGE -- A GENERIC DISPATCHING SUB
# WHICH EXPLORES THE %dispatch AND %cache HASHES LOOKING FOR A UNIQUE
# BEST MATCH...

sub make_dispatch # ($name)
{
    my ($pkg,$name) = @_;
    my $code = q{

    sub PACKAGE::NAME
    {
    # MAP THE ARGS TO TYPE NAMES, MAP VALUES TO '#' (FOR NUMBERS)
    # OR '$' (OTHERWISE). THEN BUILD A FUNCTION TYPE SIGNATURE
    # (LIKE A "PATH" INTO THE VARIOUS TABLES)

        my $sig = "";
        my $nexttype;
        foreach ( @_ )
        {
            $nexttype = ref || ( (~$_&$_) eq 0 ? '#' : '$' );
            $sig .= $nexttype;
            $sig .= ",";
        }
        chop $sig;

        my $code = $Class::Multimethods::dispatch{'NAME'}{$sig}
            || $Class::Multimethods::cached{'NAME'}{$sig};
                
        return $code->(@_) if ($code);



( run in 2.463 seconds using v1.01-cache-2.11-cpan-5511b514fd6 )