Acme::Don't 2.0!
Acme::Don'tというモジュールがある。これが提供するdon't{}は,Perl組み込みのdo{}とは逆に,ブロックの中身を実行しない。たとえば以下のコードのように使う*1。
use Acme::Don't; don't{ print "Hello, world!\n"; # => 実行されない };
これは問題ない。まさにその通りで,予想通りの振る舞いといえる。しかし,don't{}を入れ子にすると話が変わってくる。
use Acme::Don't; don't{ don't{ print "Hello, world!\n"; # => ??? }; };
現状では何も実行されないが,これは予想通りの振る舞いといえるか。2重否定は肯定になってしかるべき,つまり"Hello, world!\n"を印字してしかるべきではないか。
そこで,ブロックが与えられたとき通常は実行されず,二重否定ブロックが現れたときには実行されるようなdon't{}を考えてみた。
package Acme::Don::t; use 5.008_001; # maybe use warnings; use strict; our $VERSION = '2.0'; use B (); our $cur_pad; sub _get_gvref{ my($o) = @_; my $gv; if(ref($o) eq 'B::SVOP'){ # without threads $gv = $o->gv; } else{ # with threads $gv = ($cur_pad->ARRAY)[$o->padix]; } return $gv->object_2svref; } sub _get_cvref{ my($o) = @_; my $cv = ($cur_pad->ARRAY)[$o->targ]; return $cv->object_2svref; } sub B::OP::_dont{ } sub B::UNOP::_dont{ my($o) = @_; if($o->name eq 'refgen'){ my $op_gv = $o->sibling->first; if(_get_gvref($op_gv) == \*don::t){ my $op_anoncode = $o->first->first->next; _get_cvref($op_anoncode)->(); } } } sub don::t(&){ my $cv = B::svref_2object(@_); local $cur_pad = ($cv->PADLIST->ARRAY)[-1]; B::walkoptree($cv->ROOT, '_dont'); return; } 1; __END__
これで以下のコードが予測した通り動く。
use Acme::Don't 2.0; don't{ die 'not executed'; don't{ print "executed (1)\n"; }; die 'not executed'; don't{ print "executed (2)\n"; }; die 'not executed'; };
すばらしい!