and/or 構文による条件分岐の落とし穴 ( Perl )


Perl プログラミングにおいて、and/or を使って条件分岐処理を行う場合の留意点。

Perl は if 文の他、and/or という論理演算子を使って条件分岐処理ができる。
例えば、以下の2つの処理結果は同じになる。

my $str = ‘abcde’;
my $same = ‘abcde’;

if ($str eq $same){
print “IF: true.\n”; #こちらが print される
} else {
print “IF: false.\n”;
}

$str eq $same
and print “AND/OR: true.\n” #こちらが print される
or print “AND/OR: false.\n”;

Perl のガイドラインでは and/or の方が処理が速い、という事になっていて、実際に何百回も呼ばれるルーチンを修正すると、それだけで処理が速くなる。

という事でこの構文は非常に便利なのだが、場合によっては期待どおり動作しない場合がある。
例えば、「ハッシュ変数の個々の値をチェックして、値が空白だったらそのエントリを削除したい」とする。フォームの必須入力チェックなどで必要になる処理だ。

単純化するために処理を半角だけに限っているが、値が空白文字だけかどうかを確認する処理は以下のようになる。

my $ws = ‘ ‘; #半角ハイフン3つ
$ws =~ m/^\s*$/
and print “AND/OR regexp: true.\n” #こちらが print される
or print “AND/OR regexp: false.\n”;

これを、ハッシュの値に変えても処理は同じ。

$myhash{ws} = ‘ ‘; #半角ハイフン3つ
$myhash{ws} =~ m/^\s*$/
and print “AND/OR regexp: true.\n” #こちらが print される
or print “AND/OR regexp: false.\n”;

ハッシュのキーを削除する場合は and をつなげて書く事ができる。

$myhash{ws} = ‘ ‘; #半角ハイフン3つ
$myhash{ws} =~ m/^\s*$/
and print “AND/OR regexp delete: true.\n” #こちらが print される
and delete $myhash{ws}
or print “AND/OR regexp delete: false.\n”;

しかしここに落とし穴がある。
もしエントリが空文字だったらどうだろう?以下のコードは期待どおり動作しない。

$myhash{null} = ”; #空文字
$myhash{null} =~ m/^\s*$/
and print “AND/OR regexp delete null: true.\n” # print される
and delete $myhash{null}
or print “AND/OR regexp delete null: false.\n”; # ここも print される!

delete $hash{key} は削除した要素の値を返すが、これが空文字だった場合、「false」と認識され、次の 「or ….」が実行されてしまう。

このため、false を返す可能性のある処理を挟む場合は if 文を使わなければならない。

if ($myhash{null} =~ m/^\s*$/){
print “IF regexp delete null: true.\n”; # print される
delete $myhash{null};
} else {
print “IF regexp delete null: false.\n”; # print されない
}

以下のような代入の場合も同じ。

$str eq $same
and print “AND/OR set value 1: true.\n” #こちらが print される
and $val = 1
or print “AND/OR set value 1: false.\n”;

$str eq $same
and print “AND/OR set value 2: true.\n” # print される
and $val = 0
or print “AND/OR set value 2: false.\n”; # ここも print される!

ちなみに

この点が重要になるのは、and を複数連結する場合や、or の後の処理をきちんと分岐させたい場合(例えば die するなど)。
and の処理のみで終了してよい場合、一連の and で連結された処理の最後であれば false を返しても良い。