はじめに
先日Javascriptでコードを書いていたところ、if文で値の比較を行うコードが必要になりました。それ自体はごく普通のことだと思うのですが、普段PythonとかGolangとか書いてる自分が面食らったのが、勝手に型変換を行なって値の比較が行われることです。
Python 3.6.8 (default, Jan 14 2019, 21:12:09)
[GCC 4.2.1 Compatible Apple LLVM 10.0.0 (clang-1000.10.44.4)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> x="1"
>>> y=2
>>> x<y
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: '<' not supported between instances of 'str' and 'int'
>>>
package main
func main() {
x := "1"
y := 2
x < y
}
// 以下The Go Playground出力
// ./prog.go:6:4: invalid operation: x < y (mismatched types string and int)
//
// Go build failed.
Welcome to Node.js v12.4.0.
Type ".help" for more information.
> x="1"
'1'
> y=2
2
> x<y
true
>
上記のサンプルの通り、pythonでは型が異なる変数同士を比較すると例外を吐いて停止します。変数型があるGolangではビルドすらできません。一方、Javascriptの場合特にエラー等を出すことなく比較を行い、その結果を返しています。
Javascriptは値が等価であるかどうかを、型が同一であるかどうかを含め比較する演算子(===)があります。
Welcome to Node.js v12.4.0.
Type ".help" for more information.
> x="1"
'1'
> y=1
1
> x===y
false
> x==y
true
>
異なる型(上記の例ではstr型の"1"とint型の1)の比較で、厳密な比較(===)はfalseを、厳密ではない比較(==)はtrueを返していますね。
このように、Javascriptには厳密な等価演算子が存在する一方、比較演算子には厳密な演算子がありません。この件について色々と調べてみました。
厳密な比較の実現
まずは仕様書を確認する
厳密な比較の実現を目指すために、まずは本当にそのような演算子が存在しないのか確認してみます。執筆現在(2019年8月22日)時点での最新のECMAScriptであるECMAScript2018を参照してみます。
すると、Abstract Equality Comparison(型変換の比較)とStrict Equality Comparison(厳密な比較)、およびAbstract Relational Comparison(抽象的な関係比較)の章はありますが、Strict Relational Comparison(以下、厳密な関係比較)ないしはそれに類する内容の章は見当たりません1。
7.2.13 Abstract Relational Comparison
7.2.14 Abstract Equality Comparison
7.2.15 Strict Equality Comparison
また、Abstract Relational Comparisonの中を読んでみると、色々と書いてありますが、要するに一度プリミティブ型(stringやintegerなど、Javascriptに最初から存在する型)へ変換した上で比較を行うと記載してあります。
つまり、型が異なる値同士を比較した場合、問答無用でfalseを返すような類のものではありません(Strict Equality Comparisonの場合、比較される値同士の型が異なる場合、falseを返すと明記されています)。したがって、厳密な関係比較を行いたい場合、プログラマが何らかの手段で実装する必要があります。
何らかの手段で実装する
厳密な関係比較を行いたい場合、手っ取り早いのはif文の条件式にtypeofを混ぜ込んでしまうことです。Javascriptで変数の型が等しいか確認したい場合、以下のようなセンテンスが使用されます。
typeof(x) === typeof(y)
したがって、値の大小関係を比較しつつ、変数の型が等しいかを確認したい場合は、関係比較と上記のセンテンスの論理積を取ることになります。具体的には以下のような例になります。
Welcome to Node.js v12.4.0.
Type ".help" for more information.
> x="1"
'1'
> y=2
2
> x < y && typeof(x) === typeof(y)
false
>
ちゃんとfalseになりましたね。
そもそもなぜないんだ
Javascriptのように暗黙に型変換を行なう場合、プログラマが意図した動作とは異なるにも関わらずif文を通過してしまい、結果としてその先の処理でエラーが発生した時、問題の箇所の特定を難しくしてしまいます。ECMAはどのような理由づけの元、このような仕様を策定したのでしょうか。そのあたりの議論を確認するべく、ECMAのメーリングリストをES Discussionで確認してみました。
おそらくいくつかの議論が行われた上で、実装を却下する結論が下されたのだろうと予想していたのですが、意外なことにそもそもあまりこの件についての議論が見つかりませんでした。ひとしきり調べてみましたが、バグレポートやリンク切れの議論などを除くと、以下のディスカッションが参考になるでしょうか。
拙い英語力でひとしきり読んでみたところ、過去に議論されたことがないので検討してみよう、みたいな流れになって、後半では実装されるならどのような形態が良いかの議論になっていました。
厳密な関係演算子が却下されたのではないとすると、次はなぜ厳密な比較演算子が実装されたのかや、比較演算子自体がプリミティブ型に変換した上で比較するという実装がされたのかが気になりましたが、残念ながらそれに関する議論を見つけることができませんでした...。
終わりに
Javascriptのif文で値の関係演算を行う場合で、特に異なる型同士の比較をfalseにしたい場合はtypeofを噛ませる必要があります。
Javascriptの関係演算子の実装についての議論は見つけることができませんでした。竜頭蛇尾な結果に終わってしまい残念ですが、今後Javascriptの型変換について知見が溜まった時に、何らかの結論が得られるのではと考えています。