PHP : エクスクラメーションマーク2つによる二重否定

月花です。
ちょっとコードリーディングをしていて、なんだこれって思ったのでまとめてみます。

<?php
$hoge = !!$foo;

というように、PHPでexclamation markによる二重否定( double not ) を行うコードが見つかった。
最初は double not だと思っていたのだが、実はどちらかというと double not ではなく twice not で、「二重否定」というよりは「否定の二重適用」というような挙動だった。

毎度のことながらここまでは検索用の文なので、以降は日本人に戻ってカタカナで書きます。

  • エクスクラメーションマーク2つによる二重否定
  • なぜ二重否定でboolean型へのキャストとなるのか
  • PHPでの比較演算
  • 他のboolean型への変換手法
  • 結論:どれ使ったら良いの

エクスクラメーションマーク2つによる二重否定

<?php

$hoge = !!$foo;

というようなコードを見つけた。
2文字の演算子なので、一見比較演算子か論理演算子かと思ったのだが、コードの文脈上違うようだ。
また、代入を行っているので、なんらかのキャストのようだと思ったのだが、結論から言うと

<?php

$hoge = (bool)$foo;

と同義のようだ。

なぜ二重否定でboolean型へのキャストとなるのか

二重否定は、このように置き換えられる。

<?php

$hoge = !( !$foo );

否定を行う論理演算子を一度適用し、さらにもう一度否定している流れである。
PHPではエクスクラメーションマークによる否定ではboolean型への暗黙的なキャストが行われ、boolean型にキャストされたものに対してもう一度否定をしてやることで、キャストしつつ元々の結果に反転させることで、遠回りにキャストを実現している。
冗長に書き直すと、

<?php

$hoge = !$foo;    // 暗黙的なキャストと論理否定
$hoge = !$hoge; // 2回目の論理否定

となる。

これは、

<?php

$hoge =1;
var_dump($hoge);              // int(1)
var_dump($hoge . '');         // string(1) "1"

というような変換と似た、演算での暗黙変換を利用するキャストの手法である。

PHPでの比較演算

では、2つの変数の比較結果のbooleanを変数に押し込めたい時、どうすればよいだろうか。
PHPでは比較演算の結果は必ず返却されるため、if文の条件式中で使うだけではなく変数に代入することもできる。
つまり、

<?php

if( $hoge == $piyo ){
    $result1 = true;
}else{
    $result1 = false;
}

$result2 = ($hoge == $piyo) ? true : false;

$result3 = ($hoge == $piyo);

これらはすべて等価である。

他のboolean型への変換手法

二重否定によるキャストは、単純に2つ書いただけでそんなことが可能であると知らなければ読めないので、いささか可読性に欠けると言える。
では、他にスマートな書き方はないだろうか。
以下は全て二重否定と等価の書き方である。

<?php

// 二重否定
$hoge = !!$foo;

// 明示的キャスト
$hoge = (bool)$foo;

// 変換関数
$hoge = boolval( $foo );

// ifの利用
if( $foo ){
    $hoge = true;
}else{
    $hoge = false;
}
$hoge =$foo ? true : false;

// trueとの比較 (falseとの比較もできる)
if( $foo == true ){
    $hoge = true;
}else{
    $hoge = false;
}
$hoge = ($foo == true) ? true : false;
$hoge = ($foo == true);

// trueとの論理積
$hoge = ($foo and true);
$hoge = ($foo && true);

もっとありそうだけど、思いつくのはこれくらい。
結局読みやすくてスマートなのは、boolval() 、 (bool)、trueとの比較、 三項演算子かな、と思う。
論理積はスマートだけど、他に良い方法あるし、これをあえて選ばなくても、というくらいかな。

リテラルへの適用

ちなみに、$fooの部分はリテラルであってもよい。

<?php

$hoge = !!'0';
$hoge = !!0;
$hoge = !!(1+2+3);

ここで、ちょっとガチャガチャとやってみよう。
PHPでは、0以外の数字、及び、'0'以外または''以外の文字列リテラルはtrueとなる。
これらを . 演算子で繋ぐことによって、整数リテラルを文字列リテラルに変換することができる。
では、これと二重否定を絡めてみよう。

<?php

$foo = 1;                     // 整数リテラル
$hoge = !!( $foo.'' );  // 整数リテラルを文字列リテラルにして、それを二重否定してboolean
var_dump( $hoge );  // bool(true)

この通りたった3行で型がゴシャゴシャになるので、絶対にやるなよ!!!!!!
そんなことしねえと思ったそこのお前!!!!!
長いこと煮詰まったコードではこれらが分散して、たとえば$fooの初期化が120行目、リテラル変換が140行目、二重否定が170行目にそれぞれあったりするんだからな!!!!その間に煩雑なコードが追加されていったりするから$fooについて追うまで気づかないんだ!!!!!
絶対にこの手の成長をする種を蒔くな!!!!!!!

結論:どれ使ったら良いの

これはチームが採用している規約などに応じて変えるべきだとは思うが、規定がないのであれば、ボトムの実力に合わせるべきである。
教科書的なif文しか理解されそうもない、という環境ではやはり

<?php

if( $foo == true ){
    $hoge = true;
}else{
    $hoge = false;
}

が強いと思う。
これは教科書みたいな技術書にも最初の方に書いてあるようなif文でしかないので。

それ以上の実力があるなら、キャストかboolval()の利用。

演算結果を変数に押し込めることを知っているレベルなら、三項演算子論理積が強いと思う。
見た目にもtrueとの演算であることがすぐわかり、そもそもtrueとfalseの文字列が出てくるのでgrepや目検にも強いという有用性がある。
もしくは、何らかの定数が導入されて比較対象を変える場合にも、明示的に比較演算子やtrueなどbooleanリテラルがコードに現れているとわかりやすい。
二重否定は、それがシンタックスエラーではなく成立するということを知らない限り使えない局所的な知識なので、推奨はできない。