短絡評価
短絡評価(たんらくひょうか、英: short-circuit evaluation)または最小評価(さいしょうひょうか、英: minimal evaluation)とは、多くのコンピュータプログラミング言語の論理演算子における左辺(第一引数)と右辺(第二引数)の式の評価法のひとつである。
概要
[編集]「≪左辺≫ ≪論理演算子≫ ≪右辺≫」というような、論理演算子による式(論理演算子式)があるとする。左辺(第一引数)を評価した段階で式全体の値が定まらない場合のみ右辺(第二引数)を評価する、というのが短絡評価である。例えば、論理積 (論理AND) の第一引数を評価した結果が false
であれば、式全体は必ず false
になるし、論理和 (論理OR) の第一引数が true
であれば、式全体は必ず true
になる。いずれのケースも、第二引数を評価するまでもないので、途中で式の評価を打ち切ってかまわない。
前述のように多くの言語の論理演算子は短絡評価だが、Pascalでは「処理系依存」[1]である。Adaでは両方を評価する演算子と短絡評価の演算子とが用意されている。Javaでは、短絡評価の論理演算子の他に、ビット演算の演算子のうち&
(and) と|
(or) が、ブーリアン型にもオーバーロードされた演算子(en:Operator overloading)となっていて、これらは短絡評価をしない。C言語では、C89以前のK&Rの初版から論理演算子が短絡評価されることが明確に書かれている。C++では組み込みの型に関する論理演算子はC言語と同様、短絡評価される。ただし、ユーザー定義型に関しては &&
や ||
をオーバーロードした場合、言語仕様によって定められた短絡評価ではなくなる[2]。C++の論理演算の結果はbool
となるが、bool
同士のビット演算の結果は整数昇格によりint
となる[3]。C#では条件論理演算子をオーバーロードすることはできないが、ユーザー定義型T
の特定の演算子をオーバーロードすることで、T
を返す短絡評価の論理演算を記述することができるようになる[4][5]。Microsoft Visual Basic 6.0以前では短絡評価しない演算子のみがサポートされ、短絡評価の演算子をサポートしていなかった。しかし、後継のVB.NETでは短絡評価の演算子をサポートするようになった。
短絡評価の演算子には単なる演算子としての役割だけでなく、制御構造としての役割を負わせることができる。(なお、ALGOL 68 は "proceduring" という機能を使ってユーザー定義の短絡評価の演算子やプロシージャを実現できる)
短絡評価では、x Sand y
は、if x then y else false
というようなif式[6]のような意味となり、x Sor y
は、if x then true else y
のような意味となる。ただしこれは演算結果の値がtrue
あるいはfalse
のブーリアン型の場合である[7]。型付けが弱い言語などでは、論理演算子の引数として任意の型を使うことができ、型毎に定められている、その値が真っぽい(truthy)か、偽っぽい(falsey)か、に従って短絡評価の論理演算を行うものがある。この場合、「その式全体の真偽を決定付けた、最後に評価した引数の値」を返す。下の表の「型」のカラムではその値の型という意味で、「最後に評価した値」としている。
言語 | 非短絡評価の論理演算子 | 短絡評価の論理演算子 | 型 |
---|---|---|---|
Ada | and , or
|
and then , or else
|
ブーリアン |
ALGOL 68 | and/&/∧ , or/∨ | andf , orf - ユーザー定義 | bool |
C言語 | なし | && , ||
|
int
|
C++ | なし [8] | && , || [9]
|
bool
|
C# | & , |
|
&& , ||
|
bool [10]
|
Java | & , |
|
&& , ||
|
boolean
|
JavaScript | なし | && , ||
|
最後に評価した値 |
Lisp | なし | and , or
|
最後に評価した値 |
Perl | なし | && , and , || , or
|
最後に評価した値 |
Python | なし | and , or
|
最後に評価した値 |
Visual Basic | And , Or
|
なし | Boolean
|
Visual Basic .NET | And , Or
|
AndAlso , OrElse
|
Boolean
|
例
[編集]例として次のC言語のコードを見てみよう。
int a = 0;
if (a && myfunc(b)) {
do_something();
}
この例では、短絡評価によって myfunc(b)
は決して呼び出されない。なぜなら a
は false と評価されるからである。この性質を利用して2種類のプログラミング技法が得られる。1つは、後半の部分式に時間のかかる処理を置き、最初の部分式でその処理が必要か否かのチェックをするようにすれば、無駄な関数呼び出しなどを減らすことができる。2つめとして、後半の部分式が実行時エラーを起こすかどうかのチェックを前半で行うというコーディングが考えられる。例えば、次のC言語コードは、短絡評価を利用してヌルポインタの参照を防いでいる。
int is_three_chars_long(const char *p) {
return (p != NULL) && (strlen(p) == 3);
}
短絡評価を利用することで、コードブロックの入れ子がいたずらに深くなることを防ぐこともできる。
このような利点はあるが、短絡評価であることを忘れていると問題が発生することがある[11]。例えば、次のコードを見てみよう。
if (expressionA && myfunc(b)) {
do_something();
}
ここで、myfunc(b) は副作用のある式だとする。myfunc(b) が常に実行されることを期待していると、expressionA が false だった場合に実行されないことになり、問題が発生する。必ず実行されるようにするには、2つの式の評価順序を変えるか、あるいはまず両方の式を評価し、いったん結果を変数に格納してから論理演算するなどの対処が必要となる。
if (myfunc(b) && expressionA) {
do_something();
}
Javaなどの言語では短絡評価する演算子と短絡評価しない演算子を用意して、このような問題に対処している。
if文の条件式で使われる論理積や論理和の演算子は、if文の入れ子をコンパクトに表したものと見ることもできる。例えば、次の擬似コードの中で、&&
演算子は短絡評価を行うとする。
if (CONDITION_A && CONDITION_B) {
TRUE_BRANCH
} else {
FALSE_BRANCH
}
これは、論理積を展開することで次のようにも表せる。
if (CONDITION_A) {
if (CONDITION_B) {
BOTH_TRUE
} else {
B_FALSE
}
} else {
A_FALSE
}
ブール論理の性質から、A_FALSE と B_FALSE というコード部分が、最初の例では FALSE_BRANCH という部分に集約されていることに注意されたい。後者の例の方が条件によって異なる処理を記述できるため、必要に応じて使い分けることになる。両者が結局同じことをするコードであれば、共通式削除や定数伝播といったコンパイラ最適化によって、どちらも同じコードを生成する可能性がある。
短絡評価を使ってコードの一部の条件付実行を実現することもできる。以下は Perl の例である。
some_condition or die; # some_condition が false なら実行を停止
some_condition and die; # some_condition が true なら実行を停止
ALGOL 68
[編集]1968 Final Report での仕様と 1973 Revised Report での仕様は異なる。本来の ALGOL 68 には proceduring という機能があった。これは、項の値をその項を評価するプロシージャに合わせるようにすることである。proceduring は実質的には評価を「遅延」させることができる。その最も便利な利用法として、論理演算の短絡評価がある。
以下はユーザー定義演算子の例である。
op andf = (bool a, proc bool b)bool: if a then b() else a fi; ¢ b は a が true の場合のみ評価される。 ¢ op orf = (bool a, proc bool b)bool: if a then a else b() fi; ¢ b は a が false の場合のみ評価される。 ¢
従って、1973 Revised Report 以前には、プログラマは演算子(やプロシージャ)の引数の評価を逐次的にするか並行的にするかを選択可能であった。
ただし、これは予定通りには機能しなかった。多くの実装では andf/orf や andth/orel といった拡張を特別に扱って短絡評価をエミュレートしていた。
文献・脚注
[編集]- ^ 培風館『PASCAL (原書第4版)』 p. 34
- ^ 論理演算子 - cppreference.com
- ^ 算術演算子 - cppreference.com
- ^ && Operator - C# Reference | Microsoft Docs
- ^ Expressions | Microsoft Docs
- ^ if文ではない。一般に文は値を持たない。
- ^ C言語の場合、ブーリアン型はintで代用されており、偽値は0、また真値は0以外だが、真値の代表として1が使用される。
- ^ bool同士のビット演算はintを返す。
- ^ 組み込み型同士の論理演算に限る。
- ^ 特定の演算子オーバーロードにより、ユーザー定義型を返すこともできる。
- ^ EXP30-C. 副作用が発生する式の評価順序に依存しない