Perl eval 00 エラーのトラップ $@, //, ?: (0x26f)

セラ (perlackline)

2015年11月24日 17:19



目次 - Perl Index



Theme



Perl について、復習を兼ねて断片的な情報を掲載して行く連載その 0x26f 回。


Perl で、「 致命的なエラー 」をトラップ ( 捕捉 ) する関数「 eval 」を確認する。その 00。



エラーの発生



プログラムを実行して「 致命的 」なエラーが発生した場合、通常プログラムはそれ以降の処理を実行することなく終了します。

例えば、次のプログラムは、ユーザによって入力された数値をもとにして除算を行います。


print "please be divided by x / y.\n";
print "the dividend x: ";
chomp(my $x = <STDIN>);
print "the divisor y: ";
chomp(my $y = <STDIN>);

my $result = $x / $y;

print "result: $result\n";



このプログラムを実行すると、次のようになります。


please be divided by x / y.
the dividend x: 10
the divisor y: 5
result: 2



入力受付けの「 プロンプト 」に従って任意の数値を入力すれば、それを除算した結果を出力してくれます。

ここで、意図的にエラーを発生させてみます。エラーの原因には「 ゼロ除算 」を使います。

すると、プログラムは 9 行目の print 文を実行することなく、次のように処理を終了します。


please be divided by x / y.
the dividend x: 10
the divisor y: 0
Illegal division by zero at file.pl line 12, <STDIN> line 2.



「 Illigal division by zero ... 」は「 ゼロによる不正な除算 」のエラーが発生したというメッセージです。


エラーのトラップ



前項で確認した通り、通常「 致命的 」とされるエラーが発生した場合、プログラムは有無を言わさず終了します。

しかしながら、Perl では関数「 eval 」のブロック形式によって「 致命的 」なエラーを「 トラップ 」( trap : 捕捉 ) することで、プログラムの処理を継続しつつエラーの内容確認やハンドリングを行うことが出来ます。

エラーの「 トラップ 」は、同じ意味でエラーの「 キャッチ 」( catch ) と呼ばれることがあります。

また、「 eval 」は「 イーバル 」や「 エバル 」等と読まれるようです。僕は「 イーバル 」と読んでいましたが、rebuild.fm の miyagawa さんが「 エバル 」っぽく発音していたので、僕もこれにならって今後は「 エバル 」と読むことにします。

前項のプログラムに「 eval 」ブロックを追加して、エラーをトラップしてみます。


print "please be divided by x / y.\n";
print "the dividend x: ";
chomp(my $x = <STDIN>);
print "the divisor y: ";
chomp(my $y = <STDIN>);

# added eval block
my $result;
eval { $result = $x / $y };

print "result: $result\n";



「 eval 」によるブロックは変数のスコープ作るので、スカラ変数「 $result 」の宣言は「 eval 」ブロック外でしておく必要があります。

このプログラムを利用してゼロ除算を実行すると次のようになります。


please be divided by x / y.
the dividend x: 10
the divisor y: 0
result:



プログラムコードの 9 行目ではゼロ除算による「 致命的 」なエラーが発生しているにも関わらず、プログラムは終了せずに 11 行目の print 文が実行されていることがわかります。

なぜ、プログラムが末尾まで実行されるかというと、それは、「 eval 」がエラーを捕まえているからです。

もちろん、この時「 $result 」の値は undef なのでなにも出力されませんし、プラグマ「 warnings 」(0x15) を use しているなら警告メッセージ「 Use of uninitialized value $result in concatenation (.) or string ... 」が併せて出力されます。


「 eval 」の戻り値



関数「 eval 」のブロックは、サブルーチン (0x30) と同じく最後に評価した式の結果を返します。

ですから、前項の除算部分は次のように書き換えることが出来ます。


my $result = eval { $x / $y };



もし、「 eval 」ブロック内でエラーが発生した場合、「 eval 」は undef (0x19) を返します。

前項でプラグマ「 warnings 」を use していた場合は、「 eval 」でエラーをトラップして処理を継続しても警告が発生していましたが、これは、「 eval 」ブロックでエラーが発生したため、出力しようとしているスカラ変数「 $result 」が適切な値を持っていない ( undef を出力しようとしている ) ことから発生する警告です。

ですから、「 初めての Perl 第 6 版 」( 17.2.1 章 ) で紹介されているように、演算子「 // 」( defined-or ) を利用してエラー発生時のデフォルト値を設定すれば、プラグマ「 warnings 」による警告を抑制出来ます。

ただし、演算子「 // 」( defined-or ) は、Perl 5.10 以降でのみ利用可能です。


use 5.010;
my $result = eval { $x / $y } // 'NaN';



デフォルト値として指定した文字列「 NaN 」は、「 Not a Number 」を意味するものだそうです。

Perl 5.10 未満を利用している場合は、(0x88) で確認した三項演算子である条件演算子「 ?: 」と undef を検知する演算子「 defined 」(0x1a) で代替が可能です。


my $result = defined eval { $x / $y } ? ($x / $y) : 'NaN';




「 $@ 」でエラーメッセージを確認する



(0x26a) 等ですでに利用していますが、Perl では、発生したエラーメッセージを特殊な組み込み変数「 $@ 」で確認出来ます。


print "please be divided by x / y.\n";
print "the dividend x: ";
chomp(my $x = <STDIN>);
print "the divisor y: ";
chomp(my $y = <STDIN>);

# eval
my $result = defined eval { $x / $y } ? ($x / $y) : 'NaN';

print "result: $result\n";

# check for error
print "\$\@: $@" if $@;



このプログラムを利用して、ゼロ除算を行うと次のような結果を得ることが出来ます。


please be divided by x / y.
the dividend x: 2
the divisor y: 0
result: NaN
$@: Illegal division by zero at file.pl line 12, <STDIN> line 2.





「 die 」による任意の例外をキャッチする



前述したように、「 eval 」は関数「 die 」(0x48) による意図的なエラー ( 例外 ) もトラップすることが出来ます。

「 eval 」による「 die 」のトラップは、シグナル「 SIGALRM 」のハンドリング方法を確認した (0x26a) でも利用しています。

ですから、単純にゼロ除算のみを例外処理したいなら次のように書くことも可能です。


my $result;

if ( $y == 0 ) {

# ゼロ除算を未然に防いで die の例外を eval でキャッチ
eval { die "division by zero!\n" }

} else {

# ゼロ除算の可能性がなければ除算
$result = $x / $y;

}

if ( $@ ) {

# die による任意の例外が発生していればメッセージを出力
print "ERROR: $@";

} else {

# 例外の発生がなければ
print "result: $result\n";

}



このプログラムでゼロ除算を実行すると、次のように任意の例外メッセージが出力されます。


please be divided by x / y.
the dividend x: 1
the divisor y: 0
ERROR: division by zero!



ここでは、「 eval 」が「 die 」の例外をトラップする機能にフォーカスしているので、実際のプログラムとしては実用的ではありません。


0x26f -> 0x270 へ



次回は、関数「 eval 」でトラップ出来ないエラーについて確認します。


参考情報は書籍「 初めての Perl 第 6 版 」を中心に perldoc, Wikipedia および各 Web サイト。それと詳しい先輩。

目次 - Perl Index



















関連記事