ChatWorkのコピーサイトを調べてみました » |
PHPの比較とBOOL/NULL型の話
先日、10年以上勘違いをしていたことがPHPの開発者MLで議論して発覚したので、その大恥を披露します。
PHPの比較とBOOL/NULL型の話です。
ことの発端はmin関数で
var_dump(min(-100,-10, FALSE( or NULL), 10, 100);
とするとFALSE(またはNULL)が返ってくる、というバグレポートからでした。
PHPのBOOL/NULL型は
var_dump(TRUE == 1);
var_dump(FALSE == 0);
var_dump(NULL == 0);
はそれぞれTRUEを返します。TUREは1、FALSE/NULLは0と評価されています。
算術演算の場合、BOOL/NULL型は数値として評価されます。
var_dump(TRUE + 1); // 2
var_dump(FALSE + 1); // 1
var_dump(NULL + 1); // 1
var_dump(1 / NULLまたはFALSE ); // FALSE + 0除算エラー
このような比較と演算の動作はPHPポケットリファレンスやPHP言語プログラミング入門を執筆する際にも確認しています。
しかし、PHPの比較演算子のマニュアルを見ると
bool or null anything Convert to bool, FALSE < TRUE
と書いてあります。これをNULLがBOOLに変換されて比較される、と読んでいたのですがNULLが変換されるだけでなく、anythingの方もBOOLに変換されます。
var_dump(min(-100,-10, FALSE( or NULL), 10, 100);
とするとFALSE(またはNULL)が返ってくるのは
-10 < FALSE
が
(bool)-10 < FALSE
として比較されるため、結局
TRUE < FALSE
となり、この条件はFALSEになります。-100との比較も同様で、結局FALSE(またはNULL)が最小値になります。
正しく理解していれば当たり前のことですが、比較演算でもBOOL型は数値として評価される、と勘違いしていたので何が起きているのかさっぱり理解出来ませんでした。
C言語にはBOOL型無く(C99からは在りますが古いCにはありません)、代わりに整数を使います。同じ動作だろうと思い違いしていました。こんな勘違いをしていても10年以上全くこの勘違いによるバグもなく過ごせてきたのは自分でも驚きです。戻り値が何なのか、引数が何なのか、など常に意識していたからだとは思いますが、一度も問題にならなかったので気付きませんでした。
まとめ
BOOL/NULL型と比較すると両辺ともBOOL型として比較されます。0と1との等価比較はキャスト無しでも構いませんが、数値として比較したい場合はキャストしてから比較しましょう。
参考:
追記
Rubyの場合、論理型とnilは比較する場合、等価しかサポートしません。
2.0.0p247 :016 > 1 == true
=> false
2.0.0p247 :017 > 0 == false
=> false
2.0.0p247 :018 > 0 == nil
=> false
全て比較結果はfalseになっています。そういう仕様です。Rubyプログラマにとっては常識ですね。
Rubyで不等号を利用しようとするとエラーになります。
2.0.0p247 :019 > 1 > true
ArgumentError: comparison of Fixnum with true failed
from (irb):19:in `>'
from (irb):19
from /home/yohgaki/.rvm/rubies/ruby-2.0.0-p247/bin/irb:16:in `'
この動作も困る、という人も居るかも知れませんが仕様なので受け入れるしかありません。Rubyは何でも変えれるので以下のような感じで変えてしまう!という方法もあると思います(試そうと思ったこと無かったので、この動作を変更できた?と思って後でやってみたらできました。Ruby最高!)が、コードが読みづらくなる問題もあります。
class Fixnum
alias_method :old_add, :+
def +(other)
self.old_add(other) * 2
end
end
Pythonの場合は不等号もサポートしています。
>>> 1 == True
True
>>> 0 == True
False
>>> 1 == False
False
>>> 0 == False
True
>>> 1 > True
False
>>> 1 < True
False
>>> 0 < False
False
>>> 0 > False
False
>>> -100 > False
False
>>> -100 < False
True
>>> None == 0
False
>>> None < 0
True
>>> None > 0
False
>>>
>>> None < -100
True
PHPと同じような動作でNoneは最小と評価されていますが、Trueは1、Falseは0と評価されています。(Null、NilでなくNoneである事は重要。CプログラマはNLLL=0です。NilならLispプログラマが誤解する可能性が。Python最高!)
言語によって論理型、ヌル型の動作が異なるので新しい言語を使う場合は比較(特に不等号)でどう動作するのかを正しく理解していないと思わぬバグを作ってしまうと思います。
PHPでは数値の比較をしているつもりで、実際には違う型と比較している場合に困る事もあります。
use strict;
等とすると厳格にチェックしエラーとなるような機能があると良いかも知れません。こうするとRubyに近い動作になります。
最後になぜPerlの例が無いかというとPerlには論理型というデータ型自体が存在しないからです。他の言語にあるような論理型を使えるようにするCPANが在ります。
それぞれ良い点、悪い点があるのですが、個人的に好みの仕様はPythonですね。しかし、エンジニア必須の概念 - 契約による設計と信頼境界線で書いている開発手法を仕様である意味強制するRubyの仕様も良いと思います。なので、PHPにはuse strict
のような設定があると良いのでは?