X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FSub%2FDefer.pm;h=5044ab74c5396ca8af7a04213042bbdb99c55a9e;hb=c86c182d6a07287f428fa4edca7c05fae95a8830;hp=816190ebd99be8c484b393b67c1acc3f8bd6c8d1;hpb=9187b8624f5faa77cf54e6c8e1656a4e2e23f564;p=gitmo%2FMoo.git diff --git a/lib/Sub/Defer.pm b/lib/Sub/Defer.pm index 816190e..5044ab7 100644 --- a/lib/Sub/Defer.pm +++ b/lib/Sub/Defer.pm @@ -3,6 +3,10 @@ package Sub::Defer; use strictures 1; use base qw(Exporter); use Moo::_Utils; +use Scalar::Util qw(weaken); + +our $VERSION = '1.003000'; +$VERSION = eval $VERSION; our @EXPORT = qw(defer_sub undefer_sub); @@ -13,25 +17,100 @@ sub undefer_sub { my ($target, $maker, $undeferred_ref) = @{ $DEFERRED{$deferred}||return $deferred }; + return ${$undeferred_ref} + if ${$undeferred_ref}; ${$undeferred_ref} = my $made = $maker->(); - if (defined($target)) { + + # make sure the method slot has not changed since deferral time + if (defined($target) && $deferred eq *{_getglob($target)}{CODE}||'') { no warnings 'redefine'; + + # I believe $maker already evals with the right package/name, so that + # _install_coderef calls are not necessary --ribasushi *{_getglob($target)} = $made; } + weaken($DEFERRED{$made} = $DEFERRED{$deferred}); + return $made; } +sub defer_info { + my ($deferred) = @_; + $DEFERRED{$deferred||''}; +} + sub defer_sub { my ($target, $maker) = @_; my $undeferred; - my $deferred_string; + my $deferred_info; my $deferred = sub { - goto &{$undeferred ||= undefer_sub($deferred_string)}; + $undeferred ||= undefer_sub($deferred_info->[3]); + goto &$undeferred; }; - $deferred_string = "$deferred"; - $DEFERRED{$deferred} = [ $target, $maker, \$undeferred ]; - *{_getglob $target} = $deferred if defined($target); + $deferred_info = [ $target, $maker, \$undeferred, $deferred ]; + weaken($DEFERRED{$deferred} = $deferred_info); + _install_coderef($target => $deferred) if defined $target; return $deferred; } +sub CLONE { + %DEFERRED = map { defined $_ ? ($_->[3] => $_) : () } values %DEFERRED; + weaken($_) for values %DEFERRED; +} + 1; + +=head1 NAME + +Sub::Defer - defer generation of subroutines until they are first called + +=head1 SYNOPSIS + + use Sub::Defer; + + my $deferred = defer_sub 'Logger::time_since_first_log' => sub { + my $t = time; + sub { time - $t }; + }; + + Logger->time_since_first_log; # returns 0 and replaces itself + Logger->time_since_first_log; # returns time - $t + +=head1 DESCRIPTION + +These subroutines provide the user with a convenient way to defer creation of +subroutines and methods until they are first called. + +=head1 SUBROUTINES + +=head2 defer_sub + + my $coderef = defer_sub $name => sub { ... }; + +This subroutine returns a coderef that encapsulates the provided sub - when +it is first called, the provided sub is called and is -itself- expected to +return a subroutine which will be goto'ed to on subsequent calls. + +If a name is provided, this also installs the sub as that name - and when +the subroutine is undeferred will re-install the final version for speed. + +=head2 undefer_sub + + my $coderef = undefer_sub \&Foo::name; + +If the passed coderef has been L this will "undefer" it. +If the passed coderef has not been deferred, this will just return it. + +If this is confusing, take a look at the example in the L. + +=head1 SUPPORT + +See L for support and contact information. + +=head1 AUTHORS + +See L for authors. + +=head1 COPYRIGHT AND LICENSE + +See L for the copyright and license.