Chienomi

話題になった三項演算子

プログラミング::beginners

Twitterで三項演算子の次のようなものが話題になった

if (expr ? true : false)

これについての議論は、およそ正しい検討と、正しい指摘によって成り立っていたため、私が指摘することはあまりない。 だが、初心者プログラマのためにしっかりと説明しよう。

if文 または if式 の前提

一般的にifは次のような意味となっている。

if (expr)

後続の書き方はものによるが、おおよそ

if (expr) {
  IFTRUE
} else {
  IFFALSE
}

のような形式である。

一般的な柔軟な言語であれば、exprはどのようなものでも構わない。 C言語においても、すべての値が論理評価に利用可能であり、0が偽で、それ以外が真になるようになっている。

exprに制限がある言語はかなり珍しい。 私の記憶が正しければ、FORTRANがexprは論理式であるという制限になっていたように思う。

また、exprは論理値を返さなければならない、という制限のある言語もかなり珍しい。 私が知る限りだとNimくらいしか思い浮かばない。ちなみに、Nimは静的型の言語で、関数にも型があるタイプであり、exprが論理式でなければならないという制限があるわけではない。

この手の制限が厳格な言語といえばsh系列のシェルだろう。 条件式はコマンドリスト(sublist)であり、sublistの最終的な終了ステータスを条件として適用する。 C言語などとは逆に、0が真で、それ以外は偽である。

三項演算子の基礎

三項演算子はifと比べてもゆらぎが少なく、つぎのようになっている。

expr ? IFTRUE : IFFALSE

ifは式でなく文である(ifは値を返さない)ことが少なくないが、三項演算子は原則として式であり、値を返す。

ifの応用として利用できる場合もあるが、もう少し制限が明確で、そのようには使えないこともある。 具体的には、IFTRUEやIFTRUEに式や文を書くことを許さない(値しか書けない)ケースがある。

三項演算子の前提

三項演算子はexprの真偽によって振る舞いを変える。 つまり、三項演算子の前提としてexprの戻り値は真偽を判定できる、ということを意味する。

だから、

expr ? true : false

というのは 全くの無意味 である。

なぜならば、exprは既に真偽によって分けられているからだ。

if(expr)よりもif(expr ? true : false)が見やすい、という主張は、さすがに未熟というか、「ifの後に何がきて、それがどのように評価されるか」ということに対して慣れが足りなさ過ぎるように思われる。

前述の通り、現代で使われる大概の言語において、「exprを評価した結果が偽でなければ真」である。 そして、ifの後にはexprの真偽を問うものであるから、それがエラーにならないのであれば常にそこに書かれているものは真または偽なのである。

C言語FAQには次のような記述がある。

C言語では、どんな非0の値も真と考えられることは真実である。しか しこのことは「入力においてのみ」、すなわちブール値がくることを 期待されているところでだけなりたつ。組み込みの演算子によってブー ル値が産み出されるときは、1か0であることが保証されている。よってテスト

if ((a == b) == TRUE)

は(TRUEが1であるかぎり)期待したとおりの結果を返す。しかしバカげたことである。一般にTRUEやFALSEを相手に明示的にテストすることは望ましくない。なぜならライブラリ関数の中には(有名なのは isupper()、isalpha()など)条件が成立したときに非0の値を返すが、 その値は必ずしも1ではないものがある(さらに、もし君が「if((a == b) == TRUE)」が「if(a == b)」の改良版であると信じるのなら、なぜそこで止めるのか。なぜ「if (((a == b) == TRUE) == TRUE)」を 使わないのか)。

だから、その主張に基づくならば

if ((expr ? true : false) ? true : false)

と書くべきだろう。

どうしても関係演算子を書きたい人

なにがなんでもifに

if (i < 10)

みたいなことを書きたい人を結構見る。まるで、それしか書けないかのように。 ――いや、もしかして本当にそれしか書けないと思っているのだろうか。

FORTRANはそれに近い制限があるが、古いFORTRANは.lt.みたいな書き方だから、その継承というわけでもないだろう。

このような制限しかないという言語は極めて少なくて、 無理やりこのような書き方に直そうとするのはほぼ有害なやり方である。

その典型としてシェルスクリプトがある。 例えば

somecommand
if [ $? == 0 ]
then
  #...
fi

みたいな話である。

まぁ、このコードは(( ... ))を使わないという点でも頭が痛いが、これは単に

if somecommand
then
  #...
fi

と比べて見づらいだけでなく、意味も少し違う。

shのifにおいて評価されるのはコマンドリストの最終的なステータスコードであり、コマンドの終了ステータスコードではない。対して、$?は最後に実行されたコマンドの終了ステータスコードである。 これはほとんどの場合同じだが、微妙に違いがある。ZshではTRAPZERRしている場合などだ。

そして、実際に無理やりに比較演算子でやり通そうとするコードをご覧にいれよう。 本来はオブジェクトにプロパティがあれば良いというコードである。

if (obj.someProp) {
  // ...
}

だが、なにがなんでも比較演算子を使いたい人は例えば次のようにする。 (いや、これでもだいぶすっきり書いているが)

if (typeof(obj.someProp) != "undefined" ) {
  // ...
}

次の例では、RubyのHashオブジェクトに有効なキーがあることを確認している。

if hash[]

関係演算子にこだわるならこうなる。

if(hash.has_key?() == false && hash[] != false)

どうしてもbooleanと比較したい人

booleanというのはそれ自体が真偽を表すものであり、論理評価においては最終的な値である。 にも関わらず、関係演算子を書きたいだけでは留まらず、booleanとの等価演算子でなければ気がすまない、という人がいる。 つまり、

if (foo == TRUE)

ということである。先程の例では

if (typeof(obj.someProp) != "undefined" ) {
  // ...
}

どころか、

if ((typeof(obj.someProp) != "undefined") == true) {
  // ...
}

と書かなければ気がすまないということである。

しかしこれはなかなかリスクが高い。

expr == true

という式はほとんどの言語においてexprが真であるという意味ではない。 exprがbooleanのtrueであるという意味である。 もちろん、同様に

expr == false

もepxrが偽であるという意味になる言語は少ない。

ごく稀にexprをbooleanにするための関数を持っている言語もある。例えばJavaScriptである。

if (Boolean(foo) == true)

Boolean(expr)の結果はexprが真であればtrueに、偽であればfalseになる。 全く意味がないにも関わらず このような機能を持つことは、あたかもこのようなスタイルのプログラミングを推奨しているかのように見える。 事実、このような書き方が可能であることは、推奨はしていないにせよこのような書き方を許容しているということである。

RubyやPerlはこうしたboolean化の手段を提供していない。それどころか、Perlはboolean自体がない。 代わりに01を使うのだが、

expr eq 1

と書けばこの書き方が持っている問題に気づくことができるのではないだろうか。 しかしそれでもPerlもRubyも同じ方法でbooleanを得ることができる。次のようにすれば良いのだ。

!!expr eq 1

非常に見にくい書法だと私は感じるが、皮肉にもPerlやRubyにおいてはこのようにすることに多少の意味がある。 Perlにおいては0または1であるため、短絡評価しないAND, OR, XORとして使用することができ、Rubyでも同様のことができるのだ。真偽をbooleanのみで判断しようとすると、必要以上に(動的型付けの言語であっても)戻り値の型を意識する必要性が出てくる。そうした面倒を避けようとすると、(本来は必要でない)Boolean()のようなコードを大量に書くことになる。