FunctionalPerl

 view release on metacpan or  search on metacpan

lib/FP/Trampoline.pm  view on Meta::CPAN

    is fact(5), 120;

    use FP::Stream;
    is( fact(20), stream_iota(2)->take(19)->product );


=head1 DESCRIPTION

Perl has direct support for optimized (i.e. non-stack-eating) tail
calls, by way of `goto &$subref`, but there are still bugs in current
versions of Perl with regards to memory handling in certain situations
(see L<t/perl/goto-leak>). Trampolining is a technique that works
without reliance on any tail call optimization support by the host
language. Its drawbacks are more overhead and the requirement to put a
`trampoline`ing call around any function that employs trampolining.

=head1 FUNCTIONS

=over 4

=item T { ... }

Returns a closure blessed into the `FP::Trampoline::Continuation`
namespace, which represents a trampolining continuation.

=item TC $fn, $arg1...

Returns a `FP::Trampoline::Call` object, which represents the same
thing, but can only be used for a call ('Trampoline Call'). The
advantage is that the arguments for the call are evaluated eagerly,
which makes it work for dynamic variables, too (like `$_` or
local'ized globals). It may also be a bit faster.

=item trampoline ($value)

The trampoline that bounces back as long as it receives a trampolining
continuation: if so, the continuation is run, and the result passed to
the `trampoline` again, otherwise it is returned directly.

=back

=head1 NOTE

This is alpha software! Read the status section in the package README
or on the L<website|http://functional-perl.org/>.

=cut

package FP::Trampoline;
use strict;
use warnings;
use warnings FATAL => 'uninitialized';
use Exporter "import";

our @EXPORT      = qw(T TC trampoline);
our @EXPORT_OK   = qw();
our %EXPORT_TAGS = (all => [@EXPORT, @EXPORT_OK]);

use FP::Carp;

sub T (&) {
    bless $_[0], "FP::Trampoline::Continuation"
}

sub TC {
    bless [@_], "FP::Trampoline::Call"
}

sub trampoline {
    @_ == 1 or fp_croak_arity 1;
    my ($v) = @_;
    @_ = ();    # so that calling a continuation does not need () (possible
                # speedup)
    while (1) {
        if (my $r = ref $v) {
            $v = (
                  $r eq "FP::Trampoline::Continuation"
                ? &$v
                : $r eq "FP::Trampoline::Call" ? do {
                    $$v[0]->(@$v[1 .. $#$v])
                    }
                : return $v
            );
        } else {
            return $v
        }
    }
}

1



( run in 3.466 seconds using v1.01-cache-2.11-cpan-524268b4103 )