健康意識の高まり

いろんなメモなど

perl の goto にコードリファレンスを渡した時の動作はわかったけど、lovetest の動作はわからなかった

ainame くんの記事へのレスポンス記事を読んでいて、
「なんだこの goto は!」と気になったので調べてみた:

http://perldoc.perl.org/functions/goto.html

The goto-&NAME form is quite different from the other forms of goto. In fact, it isn't a goto in the normal sense at all, and doesn't have the stigma associated with other gotos. Instead, it exits the current subroutine (losing any changes set by local()) and immediately calls in its place the named subroutine using the current value of @_. This is used by AUTOLOAD subroutines that wish to load another subroutine and then pretend that the other subroutine had been called in the first place (except that any modifications to @_ in the current subroutine are propagated to the other subroutine.) After the goto, not even caller will be able to tell that this routine was called first.
NAME needn't be the name of a subroutine; it can be a scalar variable containing a code reference or a block that evaluates to a code reference.

goto &NAME すると、local() した変更が失われつつ、実行中のサブルーチンを終了し、リファレンスを渡したコードがそのときの @_ を引数として実行されるらしい。
ためしに実行してみた。

use strict;
use warnings;

sub lovetest {
    goto \&gototest;
}

sub gototest {
    my ($name, $func) = @_;

    &$func( "$name" );
}

lovetest 'aiueo' => sub {
    my $name = shift;
    print "$name\n";
};

実行結果は以下。

aiueo

とすると、teardown(最近覚えた言葉なので意味を取り違えている可能性あり) 的なことをするには Scope::Guard を使えば、

use strict;
use warnings;
use Scope::Guard;

{
    package Foo;

    our $var = 0;

    sub set {
        my $self = shift;
        $Foo::var = shift;
    }

    sub get {
        return $Foo::var;
    }
}

sub lovetest {

    print sprintf("var = %d\n", Foo->get() );
    my $guard = Scope::Guard->new(sub {
        Foo->set(0);
    });
    goto \&gototest;
}

sub gototest {
    my ($str, $func) = @_;

    &$func( "$str" );
}

lovetest 'aiueo' => sub {
    my $name = shift;
    Foo->set( Foo->get() + 1 );
    print sprintf("%s %d\n",$name, Foo->get() );
};

lovetest 'kakikukeko' => sub {
    my $name = shift;
    Foo->set( Foo->get() + 1 );
    print sprintf("%s %d\n",$name, Foo->get() );
};

lovetest 'sasisuseso' => sub {
    my $name = shift;
    Foo->set( Foo->get() + 1 );
    print sprintf("%s %d\n",$name, Foo->get() );
};

すればいいのかなと思って実行した結果は以下。

var = 0
aiueo 1
var = 1
kakikukeko 1
var = 1
sasisuseso 1

いけてるようで、lovetest のスコープを外れると Foo::var の値が最後に lovetest 内で設定した値になってしまうのでなんか気持ち悪いような・・・。それともこんなものなのか。まぁ lovetest 呼ぶ前提ならこれでいいのかしら。

あ、もしやだから local が必要なのか?いや、けど local() の変更は失われるしな・・・と思ったけどとりあえず書いてみて、

use Scope::Guard;

{
    package Foo;

    our $var = 0;

    sub set {
        my $self = shift;
        $Foo::var = shift;
    }

    sub get {
        return $Foo::var;
    }
}

sub lovetest {

    local $Foo::var = 0;
    print sprintf("var = %d\n", Foo->get() );
    my $guard = Scope::Guard->new(sub {
        Foo->set(0);
    });
    goto \&gototest;
}

sub gototest {
    my ($str, $func) = @_;

    &$func( "$str" );
}

lovetest 'aiueo' => sub {
    my $name = shift;
    Foo->set( Foo->get() + 1 );
    print sprintf("%s %d\n",$name, Foo->get() );
};

lovetest 'kakikukeko' => sub {
    my $name = shift;
    Foo->set( Foo->get() + 1 );
    print sprintf("%s %d\n",$name, Foo->get() );
};

lovetest 'sasisuseso' => sub {
    my $name = shift;
    Foo->set( Foo->get() + 1 );
    print sprintf("%s %d\n",$name, Foo->get() );
};

実行してみたけど、

var = 0
aiueo 1
var = 0
kakikukeko 2
var = 0
sasisuseso 3

local で退避した値を戻す処理のほうが guard の処理よりもあとのようなのでこうなってしまう。
うーむ・・・。local すると setup っぽい動きにならないような・・・。
なにかどこか根本的な勘違いや誤った理解をしているのかな・・・。