memoize.pm
来自「视频监控网络部分的协议ddns,的模块的实现代码,请大家大胆指正.」· PM 代码 · 共 1,049 行 · 第 1/3 页
PM
1,049 行
# -*- mode: perl; perl-indent-level: 2; -*-# Memoize.pm## Transparent memoization of idempotent functions## Copyright 1998, 1999, 2000, 2001 M-J. Dominus.# You may copy and distribute this program under the# same terms as Perl itself. If in doubt, # write to mjd-perl-memoize+@plover.com for a license.## Version 1.01 $Revision: 1.18 $ $Date: 2001/06/24 17:16:47 $package Memoize;$VERSION = '1.01_02';# Compile-time constantssub SCALAR () { 0 } sub LIST () { 1 } ## Usage memoize(functionname/ref,# { NORMALIZER => coderef, INSTALL => name,# LIST_CACHE => descriptor, SCALAR_CACHE => descriptor }#use Carp;use Exporter;use vars qw($DEBUG);use Config; # Dammit.@ISA = qw(Exporter);@EXPORT = qw(memoize);@EXPORT_OK = qw(unmemoize flush_cache);use strict;my %memotable;my %revmemotable;my @CONTEXT_TAGS = qw(MERGE TIE MEMORY FAULT HASH);my %IS_CACHE_TAG = map {($_ => 1)} @CONTEXT_TAGS;# Raise an error if the user tries to specify one of thesepackage as a# tie for LIST_CACHEmy %scalar_only = map {($_ => 1)} qw(DB_File GDBM_File SDBM_File ODBM_File NDBM_File);sub memoize { my $fn = shift; my %options = @_; my $options = \%options; unless (defined($fn) && (ref $fn eq 'CODE' || ref $fn eq '')) { croak "Usage: memoize 'functionname'|coderef {OPTIONS}"; } my $uppack = caller; # TCL me Elmo! my $cref; # Code reference to original function my $name = (ref $fn ? undef : $fn); # Convert function names to code references $cref = &_make_cref($fn, $uppack); # Locate function prototype, if any my $proto = prototype $cref; if (defined $proto) { $proto = "($proto)" } else { $proto = "" } # I would like to get rid of the eval, but there seems not to be any # other way to set the prototype properly. The switch here for # 'usethreads' works around a bug in threadperl having to do with # magic goto. It would be better to fix the bug and use the magic # goto version everywhere. my $wrapper = $Config{usethreads} ? eval "sub $proto { &_memoizer(\$cref, \@_); }" : eval "sub $proto { unshift \@_, \$cref; goto &_memoizer; }"; my $normalizer = $options{NORMALIZER}; if (defined $normalizer && ! ref $normalizer) { $normalizer = _make_cref($normalizer, $uppack); } my $install_name; if (defined $options->{INSTALL}) { # INSTALL => name $install_name = $options->{INSTALL}; } elsif (! exists $options->{INSTALL}) { # No INSTALL option provided; use original name if possible $install_name = $name; } else { # INSTALL => undef means don't install } if (defined $install_name) { $install_name = $uppack . '::' . $install_name unless $install_name =~ /::/; no strict; local($^W) = 0; # ``Subroutine $install_name redefined at ...'' *{$install_name} = $wrapper; # Install memoized version } $revmemotable{$wrapper} = "" . $cref; # Turn code ref into hash key # These will be the caches my %caches; for my $context (qw(SCALAR LIST)) { # suppress subsequent 'uninitialized value' warnings $options{"${context}_CACHE"} ||= ''; my $cache_opt = $options{"${context}_CACHE"}; my @cache_opt_args; if (ref $cache_opt) { @cache_opt_args = @$cache_opt; $cache_opt = shift @cache_opt_args; } if ($cache_opt eq 'FAULT') { # no cache $caches{$context} = undef; } elsif ($cache_opt eq 'HASH') { # user-supplied hash my $cache = $cache_opt_args[0]; my $package = ref(tied %$cache); if ($context eq 'LIST' && $scalar_only{$package}) { croak("You can't use $package for LIST_CACHE because it can only store scalars"); } $caches{$context} = $cache; } elsif ($cache_opt eq '' || $IS_CACHE_TAG{$cache_opt}) { # default is that we make up an in-memory hash $caches{$context} = {}; # (this might get tied later, or MERGEd away) } else { croak "Unrecognized option to `${context}_CACHE': `$cache_opt' should be one of (@CONTEXT_TAGS); aborting"; } } # Perhaps I should check here that you didn't supply *both* merge # options. But if you did, it does do something reasonable: They # both get merged to the same in-memory hash. if ($options{SCALAR_CACHE} eq 'MERGE') { $caches{SCALAR} = $caches{LIST}; } elsif ($options{LIST_CACHE} eq 'MERGE') { $caches{LIST} = $caches{SCALAR}; } # Now deal with the TIE options { my $context; foreach $context (qw(SCALAR LIST)) { # If the relevant option wasn't `TIE', this call does nothing. _my_tie($context, $caches{$context}, $options); # Croaks on failure } } # We should put some more stuff in here eventually. # We've been saying that for serveral versions now. # And you know what? More stuff keeps going in! $memotable{$cref} = { O => $options, # Short keys here for things we need to access frequently N => $normalizer, U => $cref, MEMOIZED => $wrapper, PACKAGE => $uppack, NAME => $install_name, S => $caches{SCALAR}, L => $caches{LIST}, }; $wrapper # Return just memoized version}# This function tries to load a tied hash class and tie the hash to it.sub _my_tie { my ($context, $hash, $options) = @_; my $fullopt = $options->{"${context}_CACHE"}; # We already checked to make sure that this works. my $shortopt = (ref $fullopt) ? $fullopt->[0] : $fullopt; return unless defined $shortopt && $shortopt eq 'TIE'; carp("TIE option to memoize() is deprecated; use HASH instead") if $^W; my @args = ref $fullopt ? @$fullopt : (); shift @args; my $module = shift @args; if ($context eq 'LIST' && $scalar_only{$module}) { croak("You can't use $module for LIST_CACHE because it can only store scalars"); } my $modulefile = $module . '.pm'; $modulefile =~ s{::}{/}g; eval { require $modulefile }; if ($@) { croak "Memoize: Couldn't load hash tie module `$module': $@; aborting"; } my $rc = (tie %$hash => $module, @args); unless ($rc) { croak "Memoize: Couldn't tie hash to `$module': $!; aborting"; } 1;}sub flush_cache { my $func = _make_cref($_[0], scalar caller); my $info = $memotable{$revmemotable{$func}}; die "$func not memoized" unless defined $info; for my $context (qw(S L)) { my $cache = $info->{$context}; if (tied %$cache && ! (tied %$cache)->can('CLEAR')) { my $funcname = defined($info->{NAME}) ? "function $info->{NAME}" : "anonymous function $func"; my $context = {S => 'scalar', L => 'list'}->{$context}; croak "Tied cache hash for $context-context $funcname does not support flushing"; } else { %$cache = (); } }}# This is the function that manages the memo tables.sub _memoizer { my $orig = shift; # stringized version of ref to original func. my $info = $memotable{$orig}; my $normalizer = $info->{N}; my $argstr; my $context = (wantarray() ? LIST : SCALAR); if (defined $normalizer) { no strict; if ($context == SCALAR) { $argstr = &{$normalizer}(@_); } elsif ($context == LIST) { ($argstr) = &{$normalizer}(@_); } else { croak "Internal error \#41; context was neither LIST nor SCALAR\n"; } } else { # Default normalizer local $^W = 0; $argstr = join chr(28),@_; } if ($context == SCALAR) { my $cache = $info->{S}; _crap_out($info->{NAME}, 'scalar') unless $cache; if (exists $cache->{$argstr}) { return $cache->{$argstr}; } else { my $val = &{$info->{U}}(@_); # Scalars are considered to be lists; store appropriately if ($info->{O}{SCALAR_CACHE} eq 'MERGE') { $cache->{$argstr} = [$val]; } else { $cache->{$argstr} = $val; } $val; } } elsif ($context == LIST) { my $cache = $info->{L}; _crap_out($info->{NAME}, 'list') unless $cache; if (exists $cache->{$argstr}) { my $val = $cache->{$argstr}; # If LISTCONTEXT=>MERGE, then the function never returns lists, # so we have a scalar value cached, so just return it straightaway: return ($val) if $info->{O}{LIST_CACHE} eq 'MERGE'; # Maybe in a later version we can use a faster test. # Otherwise, we cached an array containing the returned list: return @$val; } else { my @q = &{$info->{U}}(@_); $cache->{$argstr} = $info->{O}{LIST_CACHE} eq 'MERGE' ? $q [0] : \@q; @q; } } else { croak "Internal error \#42; context was neither LIST nor SCALAR\n"; }}sub unmemoize { my $f = shift; my $uppack = caller; my $cref = _make_cref($f, $uppack); unless (exists $revmemotable{$cref}) { croak "Could not unmemoize function `$f', because it was not memoized to begin with"; } my $tabent = $memotable{$revmemotable{$cref}}; unless (defined $tabent) { croak "Could not figure out how to unmemoize function `$f'"; } my $name = $tabent->{NAME}; if (defined $name) { no strict; local($^W) = 0; # ``Subroutine $install_name redefined at ...'' *{$name} = $tabent->{U}; # Replace with original function } undef $memotable{$revmemotable{$cref}}; undef $revmemotable{$cref}; # This removes the last reference to the (possibly tied) memo tables # my ($old_function, $memotabs) = @{$tabent}{'U','S','L'}; # undef $tabent; # # Untie the memo tables if they were tied.# my $i;# for $i (0,1) {# if (tied %{$memotabs->[$i]}) {# warn "Untying hash #$i\n";# untie %{$memotabs->[$i]};# }# } $tabent->{U};}sub _make_cref { my $fn = shift; my $uppack = shift; my $cref; my $name; if (ref $fn eq 'CODE') { $cref = $fn; } elsif (! ref $fn) { if ($fn =~ /::/) { $name = $fn; } else { $name = $uppack . '::' . $fn; } no strict; if (defined $name and !defined(&$name)) { croak "Cannot operate on nonexistent function `$fn'"; }# $cref = \&$name; $cref = *{$name}{CODE}; } else { my $parent = (caller(1))[3]; # Function that called _make_cref croak "Usage: argument 1 to `$parent' must be a function name or reference.\n"; } $DEBUG and warn "${name}($fn) => $cref in _make_cref\n"; $cref;}sub _crap_out { my ($funcname, $context) = @_; if (defined $funcname) { croak "Function `$funcname' called in forbidden $context context; faulting"; } else { croak "Anonymous function called in forbidden $context context; faulting"; }
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?