Yappo::タワシ このページをアンテナに追加 RSSフィード

2009-01-29

RubyのFiberをPerlでも

package Fiber;
use strict;
use warnings;
use Carp ();

sub new (&@) {
    my $code = shift;

    bless {
        code  => $code,
        block => 0,
        now   => 0,
    }, __PACKAGE__;
}

sub run (&@) {
    Carp::croak q!Can't call Fiber::run {} outside Fiber::new {} block!;
}
sub yield (&@) {
    Carp::croak q!Can't call Fiber::yeild {} outside Fiber::new {} block!;
}

sub resume {
    my $self = shift;
    return if $self->{block} < 0;

    unless ($self->{block}) {
	my @blocks;
	{
            no warnings 'redefine';
            local *yield = sub { @_ };
            local *run   = sub { @blocks = @_ };
            use warnings;
            $self->{code}->();
	}
        $self->{blocks} = \@blocks;
	$self->{block}  = scalar(@blocks);
	$self->{block}  = -1 unless $self->{block};

	$self->{now} = 0;
    }
    Carp::croak 'dead fiber called (FiberError)' unless $self->{block} > $self->{now};

    $self->{blocks}->[$self->{now}++]->(@_);
}
1;
use strict;
use warnings;
use Fiber;

my $f = Fiber::new {
    my $i = 0;
    warn "init: " . $i++;
    Fiber::run {
        warn "1";
    } Fiber::yield {
        warn "2: ($_[0]) " . $i++;
    } Fiber::yield {
        warn "3";
        return $i;
    }
};

$f->resume;
$f->resume('args');
warn "LAST: " . $f->resume;
$ perl ./mock.pl 
init: 0 at ./mock.pl line 7.
1 at ./mock.pl line 9.
2: (args) 1 at ./mock.pl line 11.
3 at ./mock.pl line 13.
LAST: 2 at ./mock.pl line 20.

俺のつくりたいのはこんなんじゃなくて外部イテレータとかも出来るようにしたいの!!

Scope::Upper

use strict;
use warnings;

{
    package Y;

    sub foo {
        warn "foo: start";
        bar();
        warn "foo: end";
    }

    sub bar {
        warn "bar: start";
        package X;
        use Scope::Upper qw( reap localize :words);
        reap sub {
            my $pkg = caller;
            warn "REAP: $pkg";
        } => HERE;

        sub {
            my $pkg = caller;
            warn "FUNC: $pkg";
        }->();
        warn "bar: end";
    }
}

warn "START";
Y::foo;
warn "END";

を実行すると

$ perl ./scopeupper.pl 
START at ./scopeupper.pl line 30.
foo: start at ./scopeupper.pl line 8.
bar: start at ./scopeupper.pl line 14.
FUNC: X at ./scopeupper.pl line 24.
bar: end at ./scopeupper.pl line 26.
REAP: Y at ./scopeupper.pl line 19.
foo: end at ./scopeupper.pl line 10.
END at ./scopeupper.pl line 32.

HERE を TOP に変えると

$ perl ./scopeupper.pl 
START at ./scopeupper.pl line 30.
foo: start at ./scopeupper.pl line 8.
bar: start at ./scopeupper.pl line 14.
FUNC: X at ./scopeupper.pl line 24.
bar: end at ./scopeupper.pl line 26.
foo: end at ./scopeupper.pl line 10.
END at ./scopeupper.pl line 32.
REAP: main at ./scopeupper.pl line 19.

HERE を SUB に変えると

$ perl ./scopeupper.pl 
START at ./scopeupper.pl line 30.
foo: start at ./scopeupper.pl line 8.
bar: start at ./scopeupper.pl line 14.
FUNC: X at ./scopeupper.pl line 24.
bar: end at ./scopeupper.pl line 26.
REAP: Y at ./scopeupper.pl line 19.
foo: end at ./scopeupper.pl line 10.
END at ./scopeupper.pl line 32.

HERE を UP に変えると

$ perl ./scopeupper.pl 
START at ./scopeupper.pl line 30.
foo: start at ./scopeupper.pl line 8.
bar: start at ./scopeupper.pl line 14.
FUNC: X at ./scopeupper.pl line 24.
bar: end at ./scopeupper.pl line 26.
foo: end at ./scopeupper.pl line 10.
REAP: main at ./scopeupper.pl line 19.
END at ./scopeupper.pl line 32.

HERE を UP 1 に変えると

$ perl ./scopeupper.pl 
START at ./scopeupper.pl line 30.
foo: start at ./scopeupper.pl line 8.
bar: start at ./scopeupper.pl line 14.
FUNC: X at ./scopeupper.pl line 24.
bar: end at ./scopeupper.pl line 26.
foo: end at ./scopeupper.pl line 10.
END at ./scopeupper.pl line 32.
REAP: main at ./scopeupper.pl line 19.

HERE を SCOPE に変えると

$ perl ./scopeupper.pl 
START at ./scopeupper.pl line 30.
foo: start at ./scopeupper.pl line 8.
bar: start at ./scopeupper.pl line 14.
FUNC: X at ./scopeupper.pl line 24.
bar: end at ./scopeupper.pl line 26.
REAP: Y at ./scopeupper.pl line 19.
foo: end at ./scopeupper.pl line 10.
END at ./scopeupper.pl line 32.

HERE を SCOPE 1 に変えると

$ perl ./scopeupper.pl 
START at ./scopeupper.pl line 30.
foo: start at ./scopeupper.pl line 8.
bar: start at ./scopeupper.pl line 14.
FUNC: X at ./scopeupper.pl line 24.
bar: end at ./scopeupper.pl line 26.
foo: end at ./scopeupper.pl line 10.
REAP: main at ./scopeupper.pl line 19.
END at ./scopeupper.pl line 32.

HERE を SCOPE 2 に変えると

$ perl ./scopeupper.pl 
START at ./scopeupper.pl line 30.
foo: start at ./scopeupper.pl line 8.
bar: start at ./scopeupper.pl line 14.
FUNC: X at ./scopeupper.pl line 24.
bar: end at ./scopeupper.pl line 26.
foo: end at ./scopeupper.pl line 10.
END at ./scopeupper.pl line 32.
REAP: main at ./scopeupper.pl line 19.

といった感じで指定したスコープの末尾にぶっ込む、同じスコープだとunshiftな感じで突っ込んでく。

他にも、特定のスコープ変数をlocalしたり要素のdeleteとしたりとかとか。

下位スコープで、上位スコープのlocal *subname = sub {}; なんてのも出来るから、その辺使って変態できそう。

return 文の前に return する値を先にセットしちうっぽい unwind と 特定のスコープの wantarray がとれる want_at ってやつがあるんだけど、解釈あってるかよくわからんちん。