月別アーカイブ: 2014年2月

PHP、JavaScriptにおける三項演算子(条件演算子)

ライブラリなどの出来合いコードを読解するようになると、割と目にすることも多い三項演算子(条件演算子とも)。条件分岐のifクローズをただの式に変えてしまうということで、その使用には抵抗がある向きも多いと思う(実際私もあまり好きではない)。ただ、三項演算子イコール単純なifクローズの省略記法というわけではなく、少々のメリットも存在する。その辺りを含めて、PHPとJavaScriptにおける三項演算子の使い方をまとめてみることにした。

三項演算子の基本的使用法

三項演算子については、PHP、JavaScriptともにC言語と同じ”?”や”:”といった演算子を使う。とりあえずPHPの場合で説明すると、

//ifクローズを使った場合
if($age >= 20){
$alcohol = "OK";
} else {
$alcohol = "NG";
}
 
//三項演算子を使った場合
//(式1) ? (式2) : (式3);
//(式1)が真であるとき(式2)、偽であるなら(式3)
$alcohol = $age >= 20 ? "OK" : "NG";

という具合になる。JavaScriptの場合は変数に$がつかない程度の違い。

三項演算子に慣れて親しんでいないと、直感的に理解できず少し混乱してしまう部分もあるかもしれない。上の例で見ると、変数$bへの代入で使われる”=”と、(式1)であるところの”$a <= 100"のような条件式で使われる"<="等の記号が字面上ややこしく見える。 三項演算子は算術演算子のように式を評価した結果を返すと考えなければならない。つまり、

$b = $a * 100;

こういった代入式と同じで、右辺を評価したが代入されているのだ。三項演算子は代入演算子より優先度が高い。

三項演算子に値ではなく式を入れる

ところで、三項演算子を用いた例として、以下のようなものもあり得る。PHPでの例。

//例1
$age >= 20 ? $alcohol = "OK" : $alcohol = "NG";
 
//例2
$age >= 20 ? print "OK" : print "NG";

これは、前回PHPの言語構造を説明した際に出てきた式とは何かというところで、式とは値をもつものという定義により、文法的にパスすると書いた例だ。もちろんJavaScriptの式の定義も同じであり、たとえば

//例1
age >= 20 ? alcohol = "OK" : alcohol = "NG";
 
//例2
age >= 20 ? alert("OK") : alert("NG");

このような式の代入が可能だ。

エルビス演算子(PHP5.3以降のみ)

PHPの場合、三項の真ん中の(式2)を省略して、(式1)がTRUEである場合値も(式1)になる書き方が出来る。この場合、”?:”という演算子を使う。時計回り90度回転したときに誰かさんの顔に見えるので、エルビス演算子と呼ばれる。

//(式1) ?: (式3);
//(式1)が真であるとき(式1)、偽であるなら(式3)
$tobaccoOkFlag = $alcohol == "OK" ?: 0;
 
//つまり、こう書いているのと同じ
$tobaccoOkFlag = $alcohol == "OK" ? 1 : 0;

ただし、PHP5.3以降でないと使えない。

三項演算子の右結合と左結合

三項演算子を多重に用いる場合、評価する順序が問題となってくる。標準的な順序は右結合と呼ばれる、右から評価していく方法。JavaScriptは右結合なので、JavaScriptの例を書く。

//右結合の場合
tobaccoOkFlag = pregnantFlag ? 0 : alcoholOkFlag ? 1 : 0;
 
//評価順序を分かりやすく書くと
tobaccoOkFlag = pregnantFlag ? 0 : ( alcoholOkFlag ? 1 : 0 );

この場合、多重に用いられた三項演算子のうち、右側のものが優先して評価される。alcoholOkFlagが0の場合は、pregnantFlag、つまり妊婦かどうかに関係なくtobaccoOkFlagは0になる。
一方、PHPはこれとは逆の左結合になるのだが、PHPの場合で解釈してみよう。

//左結合の場合
$tobaccoOkFlag = $pregnantFlag ? 0 : $alcoholOkFlag ? 1 : 0;
 
//評価順序を分かりやすく書くと
$tobaccoOkFlag = ( $pregnantFlag ? 0 : $alcoholOkFlag ) ? 1 : 0;

$pregnantFlagつまり妊婦であるかどうかを評価して、妊婦なら0、そうでないなら$alcoholOkFlagの値によって$tobaccoOkFlagが決まる。例があまり直感的なものに出来なかったので、申し訳ない気持ちでいっぱい。

左結合になる言語はほぼPHPのみ。というのも、PHPが左結合になってしまっているのは言語仕様を決めた際の間違いをひきずっているらしい。今更変えられないので、今後もPHPの短所として残り続けるだろう。

右結合の場合のif-elseの書き方

三項演算子が右結合だとif-elseがスッキリ書けるというメリットがある。右結合のJavaScriptの例。

//if-elseを使う場合
if(age >= 65){
message = "高齢者料金になります。";
} else if(age >= 20){
message = "大人料金になります。";
} else if(age >= 6){
message = "小人料金になります。";
} else {
message = "幼児料金になります。";
}
alert(message);
 
//三項演算子を使う場合
message = age >= 65 ? "高齢者料金になります。"
: age >= 20 ? "大人料金になります。"
: age >= 6 ? "小人料金になります。"
: "幼児料金になります。";
alert(message);

さらに、ifクローズが制御構造であるのに対して、三項演算子は式であるため、関数の直接の引数にすることも出来る。

//三項演算子を引数に
alert(age >= 65 ? "高齢者料金になります。"
: age >= 20 ? "大人料金になります。"
: age >= 6 ? "小人料金になります。"
: "幼児料金になります。");

ここまでスッキリするのなら、三項演算子を導入することも検討してよいのではないだろうか。
逆にPHPでは、三項演算子を多重に使用する事は避けた方が良いのかもしれない。


PHPのechoとprintは言語構造だというけど、言語構造とは何か

細かいことが気になり出すと、調べ尽くして解答を出すまで次の事に移れないタイプで。
PHPを覚えたての頃、標準出力にテキストや変数などを書き出す際に使う命令、echoとprintがあるということを学んだ。そして、解説書に書いてある通り、これらの命令はそれほど違わないので好きな方を使えば良いと理解した。

たまたまPHPにおける三項演算子を調べていたとき、echoとprintで挙動が異なるという例に当たった。三項演算子については、まとめ直した投稿をそのうち上げる予定だけれど、”?”や”:”といった演算子で挟まれる部分は、式でなければならないらしい。そして、echoなどは式という条件を満たさない言語構造であるから、入れるとエラーが出ますよ。ちなみにprintも言語構造ですよ。でも、関数のように振る舞うからエラーが出ませんよ。といったよく分からない説明があった。

//エラーにならない(理由:式だから)
$alcohol = $age >= 20 ? "OK" : "NG";
echo $alcohol;
 
//エラーにならない(理由:式だから)
$age >= 20 ? $alcohol = "OK" : $alcohol = "NG";
echo $alcohol;
 
//エラーにならない(理由:式だから)
$alcohol = $age >= 20 ? htmlentities("OK") : htmlentities("NG");
echo $alcohol;
 
//エラーになる(理由:言語構造だから)
$age >= 20 ? echo "OK" : echo "NG";
 
//エラーにならない(理由:言語構造だけど関数のように振る舞うから)
$age >= 20 ? print "OK" : print "NG";

PHPの式とはなんだろう?

上の3つの例でエラーにならないのは、三項演算子の書式(式1)?(式2):(式3);これに即しているため。ではそもそもPHPにおける式の定義とは何だろうか。マニュアルを見るとちゃんと一項目があって式とは値があるものだそうです。
一番上の例、”OK”や”NG”というのは、値である。これはとても直感的にわかる。
上から2番目は代入式だ。ここで結構ショッキングな事実になるけれども、実は代入式では、左辺の$alcoholに右辺の値”OK”が代入されるのみならず、式自体も右辺と同じ値を持つということらしい。
したがって、代入式は値を持っているのでエラーにならない。
上から3番目。関数を呼んで実行するのも式だ。htmlentities関数はstring型を返り値として返す。また、特に値を返す設定をしていない関数(たとえば、ユーザ定義関数でreturnを行わないタイプ)でも、自動的にNULLを返す設定となっている。つまり値を返すから式だ。

言語構造とは

では、関数ではない言語構造と呼ばれるものは一体なんなのだろう。言語構造(Language Constructs)とは、英語で見れば何のこともない。言語を構成する要素である。ifとかwhileとかといったものと同じ。制御構造を作るためのifやwhileとともに、言語に最初から存在するものとしてハードコードされているため、関数のように決まった形でないと呼び出せないというような制約はない。echoはecho “文字列”;のようにもecho(“文字列”);と関数のようにも呼び出せるが、これは便宜のためたまたまどちらの書き方でも呼び出せるようにハードコードされているからで、echoの属する言語構造というカテゴリがこうした文法的制約を持っているわけではない。
関数ではないということで、当然のことながらcall_user_func(“echo”,”文字列”);こういった呼び出し方も出来ない。

echoとprintの違いとは

では、echoとprintが両方とも言語構造でありながら、異なるというところはどこだろうか。実は先程echoが便宜のため関数のように括弧を付けても呼び出せると書いたが、printはさらに返り値として常に1を返してくる。つまり、printの方が関数への擬態がうまい。そしてその分処理が重い。
そして、何らかの値を返すということは、printは式になるのである。そのため、三項演算子の中の式としても使えていたのである。

なるほどよく分かった。ちなみに違いとしてもう一つ、echoはecho”値1″,”値2″;のように使う事も出来るというものがあり、大抵の解説書にはechoとprintの違いとしてこちらが説明されているのではないかと思う。そっちの方が重要かもしれないね。

関数っぽいけど言語構造のもの一覧

最後に、一見関数のように見えて実は言語構造という例をいくつか挙げておこう。

返り値の型 名前 機能
なし echo 文字列を出力
int print 文字列を出力
array array 配列を生成
array list 配列の形式で、変数に値を代入
mixed eval 文字列をPHPコードとして評価
なし unset 変数を破棄する
bool isset 変数がセットされているか調べる
bool empty 変数が空かどうか調べる
なし exit スクリプトの実行を終了
なし die exitと同等
mixed include ファイルを読み込む
mixed require ファイルを読み込む(読み込めないと停止)

同列に扱っているけど、既に説明したとおり共通した呼び出し方の制約はない。フリーダムなやつらなので注意が必要。