今日はVBAのIIF関数で、なぜ全ての引数を評価してしまうのか説明する。
三項演算子とIIF関数
突然だが、読者の皆さんは三項演算子という言葉を知っているだろうか。
string x = a ? b : c;
これは、a
が真の場合b
を。偽の場合c
を。x
に代入するという意味の式だ。
a,b,cを具体的な表現にするとこうなる。
string x = 10 > 5 ? "はい" : "いいえ";
それでも三項演算子を求めて検索すると、代替手段としてIIF関数にたどり着く。(Inline IF関数
)
しかし、他言語を経験したプログラマは、ショートサーキット*1が働かない事を知り、大変に驚かれるようだ。
逆にプログラミングの初学者などは、単に「IIF関数の挙動がおかしい」ということで質問する人が数多くみられる。
この疑問に対して、今まで「何故このような挙動になるのか」を初学者にも納得していただける密度で説明する余裕がなかった。
そんな人達に「IIF関数について正しく認識していただきたい」と思ったため、私の言葉で説明することにした。
IIF関数の問題点?
IF文をIIF関数に置き換えた際に問題が起こりうるプログラムは、以下ようなものである。
IIF置き換え前
Sub Test_If_Then() Dim result If 10 > 5 Then result = MsgBox("いえーい!", vbYesNo) Else result = MsgBox("うわーん!", vbYesNo) End If Debug.Print result End Sub
IIF置き換え後
Sub Test_IIF() Debug.Print IIf(10 > 5, MsgBox("いえーい!", vbYesNo), _ MsgBox("うわーん!", vbYesNo)) End Sub
前者ではメッセージボックスが一度しか表示されないが、後者では二度表示されてしまうのである。
IIF関数とは
まず、最初に頭に入れてもらいたいのは、
IIFは関数
ということ。
大事なことなので繰り返す。
IIFは関数なのである。
決して「演算子」等では無いのだ。
さて関数とはなんだったか。
VBAにおいて、関数と言えばFunctionで宣言されたプロシージャの事だ。
例えばこんなのがあったとする。
'a_bを返す関数 Function concat(a As Variant,b As Variant) As Variant concat = a & "_" & b End Function
呼び出し側はこうしよう。
?concat("前半の文字", "後半の文字")
結果はこうなる。
前半の文字_後半の文字
流石に当然か。
じゃあこうしよう。
?concat(InputBox("aは?"), InputBox("bは?"))
そして、二度表示されるプロンプトには、こう入力しよう。
a b
結果はこうなる。
a_b
そりゃ当前だ!
と、納得していただいた所で・・・
ではIIFに話を戻そう。
IIFは関数である。
つまり、Function
で自作することもできるわけだ。
試しにIIF関数と同じ動きをするIIFF関数を作ってみよう。
(尚、語源が気になる人には申し訳ないが、名前に深い意味はない。)
Function IIFF(hoge As Boolean, truePart, falsePart) If hoge Then IIFF = truePart Else IIFF = falsePart End If End Function
で使ってみる。
?IIFF(10 > 5, "10は5より大きい", "10は5より小さい")
結果
10は5より大きい
これも当然だ!と思えるはず。
では、このようにしたらどうだろう?
?IIFF(10 > 5, InputBox("Trueの時は?"), InputBox("Falseの時は?"))
すると、プロンプトは二度表示されるので、こう入力しよう。
aaaaa bbbbb
結果はこうなる。
aaaaa
あなたは「二度表示されるのは当たり前」と感じますか?
まだ納得行かない人の為に、更にもう一例。
?IIFF(10 > 5, MsgBox("いえーい!"), MsgBox("うわーん!"))
そして、メッセージボックスは二度表示される。こんなふうに。
--------------------------- Microsoft Excel --------------------------- いえーい! --------------------------- OK --------------------------- --------------------------- Microsoft Excel --------------------------- うわーん! --------------------------- OK ---------------------------
そして、出力はこうなる。
1
「両方とも表示されるのは当たり前」と感じますか?
「出力が一つだけなのは当たり前」と感じますか?
IIFは関数である。
VBAの関数において、関数の実引数に記載した式は関数の中の処理が始まる前に全て評価され、値が仮引数に引き継がれる。その後、関数の中で仮引数を使って何らかの処理をして、結果を返すのである。
今まで納得の行かなかった人は、この基本原則を忘れているのではないだろうか。もし知らなかった人は、実際にプログラムを動かして変数の中身をよく見ると良いかもしれない。
「VBAのIIF関数はショートサーキットをサポートしていないから不便だ」
それは違う。前提が間違っている。
「VBAの関数がショートサーキットをサポートしていることはあり得ない」のだ。
何故ならば、事前評価を行わない機能を備えたそれは関数足り得ないからである。
仮に実現しようと思うと、関数の仮引数の値は不確定のまま関数を通り抜けなければならない。いわゆる遅延評価という概念が必要になるが、残念ながらVBAにはそんな概念は無いのでそれは無理な相談だ。
参考資料
IIf では、 truepart または falsepart のいずれか一方だけが返されますが、評価は両方の引数に対して行われます。 このため、予期しない結果が起きることがあります。 たとえば、 falsepart を評価した結果 0 による除算エラーが発生する場合は、 expr が True であってもエラーが発生します。
IIF関数(Visual Studio)?redirectedfrom=MSDN)
IIf 関数は、Visual C++ の三項の Conditional Operator: ? :) と同じように利用できます。
Visual Basic 2008 には、ショートサーキット評価を使用する新しい If 演算子が導入されました。詳細については、「If 演算子)」を参照してください。
VBA側のヘルプではこの問題についてわかりやすく説明されているが、VisualStudio側の説明は誤解されそうな表現になっている。
まとめ
VBAのIIF関数の挙動は、決しておかしなものではない。
IIFが関数である以上、このような挙動になるのは仕方がないことなのだ。
御存知の通りIIF
はIF~Then~
に置き換えられるとは限らない点に留意して使う必要がある。
また、IIF関数にMsgBoxや自作関数などの処理を詰め込むと、一行が冗長になるはずだ。
その書き方は、本当にリーダブルなのだろうか?
多くの場合は、If~Then~End If
に展開したほうが読みやすいのではないだろうか?
一体なんのためにIIF関数を使っているのか、今一度考えてみよう。
※この説明で分からない事があれば、遠慮なく指摘して欲しい。
※VBA以外の言語をあまりこなしていないので、もしかしたら的外れな説明が含まれているかもしれない。不適切な表現があれば、遠慮なく指摘してほしい。
以上
何か御座いましたらコメント欄、またはTwitterからどうぞ♪
それではまた来週♪ ちゅんちゅん(・8・)
*1:ショートサーキット(短絡評価)とは、明らかに計算が不要な部分は評価しないようにしてくれる便利な機能。(適当)詳しくはググって