camel

これまでのあらすじから。

これ、それぞれに欠点がありました。Devel::CallerはXSに依存する。弾方式はソースフィルター、id:fbis方式はいちいち自分自身をスタックに積み直している...

この欠点を全て取り除く方法を発見しました。

[Run via codepad]
#!/usr/local/bin/perl
use strict;
use warnings;
our $CALLEE = 'whatever';    # just a place holder

sub recsub(&) {
    my $code = shift;
    sub {
        local *CALLEE = \$code;
        $code->(@_);
    }
}

my $fact = recsub { $_[0] <= 1 ? 1 : $_[0] * $CALLEE->($_[0]-1) };
my $fib  = recsub { $_[0] <= 1 ? 1 : $CALLEE->($_[0]-2) + $CALLEE->($_[0]-1)};

warn $fact->($_) for ( 1 .. 10 );
warn $fib->($_)  for ( 1 .. 10 );
warn $CALLEE;

XSに依存しません。ソースフィルターにかけません。スタックに余計な物は積みません。

で、よろこびいさんで CPANize しようと思ったら....先達がいました。

全く同じ発想です。$CALLEEでなくて$RECではありますが。

ちなみに、myではなくourを使っている理由は、モジュール化した際にexportすることを考えていたから。それがなければ

[Run via codepad]
my $CALLEE; # just a place holder

sub recsub(&) {
    my $code = shift;
    sub {
        $CALLEE = $code;
        my $ret = $code->(@_);
        $CALLEE = undef;
        $ret;
    }
}

でも行けます。

Dan the Wheel Reinventing Perl Monger