Perlにはevalブロックというのがある。
普通evalというと文字列で与えられたコードを実行するというものですけどね。
eval 'print $x+$y;';
これとは似て非なるもの。確かにこれと同じ事はできるのだけどね。
evalブロックは、違うものだがC#とかJavaとかのtry~catch構文のようなもの。
my $x=<>; chomp $x;
my $y=<>; chomp $y;
my $z;
eval{
$z=$x/$y;
1;
} or do{
print "Error : $@";
exit;
};
print "$x/$y=$z\n";
Perlのプログラムを動かしてると途中で死んでしまうことがある。
けどevalブロックの中で動かしてると、evalブロックの返り値が空リスト、すなわち偽になるだけで、プログラム自体は死なない。
最後まで処理を終えたときの返り値が真になるようにしておけば、死んだときはorなどでエラー処理ができる。
エラーの情報は特殊変数$@に入る。
そういう利点があります。
まぁあんまり使うところはないのですが、パッケージの読みこみ時にパッケージが無かったときの処理を入れられるといいなと。
例えばList::MoreUtils::minmaxを読み込めればそれを使い、
そうでなければList::Utils::minとList::Utils::maxを別々に使うとか。
まぁそういう選択があってもよいのではないかということです。
それにuseのときに死んだら、”Can’t locate List/MoreUtils.pm in @INC”とかいう微妙な表示でしょ。
どちらかというと、”List::MoreUtilsが必要です。導入してください。”の方がうれしい。
まぁこういうことができてほしいということです。
しかしこんなの簡単だろと。
eval{ use NoPackage qw(foo bar); 1; } or die '親切なメッセージ';
これでいけるんじゃないかなと。
しかしそうなる前に死んでしまった。あらー、なんでだろ。
調べるとuseというものはこれと等価らしい。
BEGIN{
require NoPackage;
import NoPackage qw(foo bar); # NoPackage->import( qw(foo bar) );
}
このBEGINブロックというのは、コンパイル時に実行されるものらしい。
そのためevalブロックとして実行される前にuseは実行されて、そこで死んでいると。
これはrequireでモジュールをロードすることと、importで必要に応じてインポートしてもらうこと。
実は#以降に書いたことと一緒です。そのためimportというメソッドを用意して、
これの第一引数にnew同様パッケージ名が来ることだけ考えておけば、
インポート以外のためにもuseのとき渡すリストは活用できるようです。
このimportというのは特殊な関数じゃなくて、そのパッケージのimportメソッドを呼び出しているに過ぎないわけね。
このような表記法はimportやnewに限らずなんでもいける。uniqでもいけます。
my @a=uniq List::Util qw(foo bar foo List::Util);
#List::Util::uniq('List::Util' , qw(foo bar foo List::Util) )
print join(',' , @a) , "\n";
まぁそれはともかく、必ずしもコンパイル時に実行しなければならないものでもないから、
こうすればOKです。
eval{
require NoPackage;
import NoPackage qw(foo bar);
1;
} or die '親切なメッセージ';
こうすると親切なメッセージを吐いて死ぬ。
ところでこんな風になった理由はevalブロックの中身も始めにコンパイルしているからで、
文字列をevalした場合はこうではない。
eval 'use NoPackage; 1;' or die '親切なメッセージ';
特に問題なく動く。この場合はevalを実行するときになってコンパイルするから。
けどこんな風に途中でevalを入れるのは効率が悪いらしい。
あとコンパイル時にエラーに気づけなかったり。
というわけなのでevalブロックの方が都合がいい。
ちなみにuseできなかったときは別にサブルーチンを定めようというのなら、
そのサブルーチンを定める部分は文字列でコードを与えるevalを使う必要がある。
というのもサブルーチンの定義ってコンパイル時にやるみたい。
だからサブルーチンの宣言よりも前に使うことができたりする。
そんなわけでこれを応用して作ってみた例。
eval{ minmax(1,6,7,5); } or print "miss\n";
eval{
require List::MoreUtils;
import List::MoreUtils qw(minmax);
1;
} or eval <<'EOF';
sub minmax{
#print "Call pure perl\n";
my ($min,$max);
$min=$max=shift;
while(@_){
my ($minelem,$maxelem);
$minelem=$maxelem=shift;
if(@_){
my $elem=shift;
if($maxelem < $elem){ $maxelem=$elem; }
else { $minelem=$elem; }
}
$min=$minelem if($min>$minelem);
$max=$maxelem if($max<$maxelem);
}
return ($min,$max);
}
EOF
my ($min,$max)=minmax(6,8,15,7,3,10,5,10,4,9);
print "min : $min\nmax : $max\n";
minmaxの定義をevalブロックでやると、それより前のevalでminmaxを実行しているところでも、
ここで作ったPerl版minmaxが動いてしまう。というわけでコードを文字列で与えると。
これでList::MoreUtilsを読み込めなかったときの対応もできてると。
ちなみにminmaxのアルゴリズムだが、
要素を1つ取り出して、これを暫定最大・暫定最小にする。
そして要素を2つ取り出して、このうち大きい方を暫定最大と比較して必要に応じて入れ替えて、
小さい方を暫定最小と比較して必要に応じて入れ替えて、というのを要素がなくなるまで繰り返し。
これで要素2つあたり3回の比較で済むという寸法。
こんなのList::MoreUtilsにあるからそれでいいじゃないのというのはもっともな話だが、
知っておくとお得なこともあるかも。