TL;DR
結局CoffeeScriptかよ!
君が思っているのと違う!
JavaScriptではa = b || c;
という書き方を見かけることがあります。その特殊形でa = a || b;
というのも見かけます。ですが、これは本当にあなたが意図した書き方なのでしょうか?
a = b || c;
という書き方について「もしb
が存在するならa
にb
を代入し、存在しないならc
を代入する」と解説される場合がありますが、これは間違いです。正しくは「もしb
が真とみなされるのであればa
にb
を代入し、偽とみなされるのであればc
を代入する」という意味です。ここで重要なのはJavaScriptにおいて何が真や偽とみなされるかです。
偽であるからと言って存在しないというわけでは無い。
JavaScriptのような動的型付け言語では全てのオブジェクト(ここではプリミティブ値を含む広義の意味です。以下、オブジェクトと表現した場合はこの意味を表します。1)について、条件分岐等の条件になったり、論理和・論理積の演算対象になったりすることができます。この場合、何をもって真であるか、偽であるかというのが重要です。JavaScriptでは次のものが偽とみなされます。
undefined
null
false
-
+0
,-0
,NaN
-
""
(空文字列)
上記以外は全て真とみなされます。ここで注目すべくはundefined
、null
、false
以外も偽であると言うことです。
undefined
は未定義である(未代入である)状態を表します。null
はなんらかの意味のあるオブジェクトでは無い状態を表します。他のプログラミング言語ではこの二つを区別せずに一つのオブジェクトで表現することが多いのですが、JavaScriptでは歴史的な経緯で二つに分かれています。この二つは非存在を表現しており、偽であることは納得できるものです。
false
は偽の代表であるため、偽であることは疑いようがありません。
では、+0
、-0
、NaN
、""
はどうなのでしょうか?他の数値や空では無い文字列に比べればそれは偽であっても良さそうなものです。それなら空のArray[]
や空のObject{}
も偽であっても良かったような気もします。なんとも中途半端ではありますが、無理矢理納得するとしましょう。
さてここで考えるのは「存在する」「存在しない」という判定において上で示す真・偽の基準で決めるのが妥当かどうかです。false
が仕方が無いとしても、+0
や""
が存在しないとするにはいささか不自然です。色即是空・空即是色ではありませんが、零は存在するし、非数値も存在するし、空文字列もやはり存在します。
無視される存在。
では、何が問題なのでしょうか?次のコードを見てください。
let a;
let b = 0;
let c = 1;
a = b || c
a
は1
になります。b
が存在するにも関わらずです。0
は全く意味の無い数値では無く、0
には0
という価値があります。それを無視してしまっているのです。
そう、問題は0
が""
が意味を持つときに、存在するかどうかと言う考えて使ってしまっていては問題が出ると言うことです。
const div = (a , b) => {
if (b != 0)
return a / b;
else
return;
};
let s = 0;
let t = 2;
let x = div(s, t) || 42;
console.log(x);
div
関数は0除算を考慮して第2引数が0
の時はundefined
を返すようになっています。x
はdiv
関数の結果がundefined
である時を考慮してundefined
だったときは42
になるようにしたつもりです。しかし、この演算の結果はもちろん42
です。決して0
ではありません。果たしてこれは意図通りの動作なのでしょうか?そんなことは無いでしょう。ぱっと見、きっと0
になって欲しいと思っていたはずです。
a = b || c;
はバッドノウハウなのかも知れない。
PHPの??
のように真のnull合体演算子であれば良かったのですが、||
は(真偽値への変換をしない)ただの論理和です。ただの論理和であっても、Rubyがnil
とfalse
しか偽とみなさいように、せめてnull
とundefined
とfalse
だけが偽であればある程度有意義だったかも知れませんが、0
や""
も偽となってしまうためnull合体演算子として使うには無理があります。そう、JavaScriptには「もしb
が存在するならa
にb
を代入し、存在しないならc
を代入する」ということを簡潔に書く方法は存在しません。a = b != null ? b : c;
のように三項演算子などを駆使して書く以外に方法は無いのです。
a = b || c;
は本当にあなたの意図した通りの記述でしょうか?もし、「存在」というキーワードで考えているのであれば、それは誤りです。JavaScriptにおいて偽であることと存在しないことは同じではありません。あなたのコードにおいて零や空文字列が意味をなそうとしても、単純に||
を使うとその意味がかき消されて、予期せぬ動作を引き起こすかも知れません。2
【おまけと言う名の本編】CoffeeScriptの逆襲。
CoffeeScriptなら存在演算子(Existential Operator)?
があります。
-
a ? b
…a
が存在するならa
、存在しないならb
-
a ?= b
…a
が存在しないときのみb
をa
に代入(a = a ? b
とほぼ同じ3) -
a?
…a
が存在するなら真、存在しないなら偽 -
a?.b
…a
が存在するならa.b
、存在しないならundefined
-
f?(x)
…f
が存在するならx
を引数にしてf
を呼出し、存在しないなら呼び出さずにundefined
上記の通り、||
にはない様々な有意義な動きをします。ここでの「存在する」とはundefined
でもnull
でも無い事を意味し、コンパイルではa != null
等の比較が使われます。
こんなに便利なのに誰も使わないのがCoffeeScriptの醍醐味です。
-
ECMAScript仕様書ではオブジェクト(object)はオブジェクト型(Object Type)のもののみを指しますが、広い意味ではプログラミングにおいて操作等の対象となる何からのもののことを言います。端的に言うと変数が束縛されうるものです。オブジェクト指向はそのオブジェクトを中心に添えてプログラミングしていくパラダイムだから、オブジェクト指向と言っているのであり、オブジェクトという考えがあってこそオブジェクト指向が産まれたのであり、オブジェクト指向の用語としてオブジェクトという概念が産まれたのではありません。 ↩
-
このことはPythonにも言えることです。Pythonにもnull合体演算子が無く、
0
や""
が偽になります。 ↩ -
a ?= b
と言う表記ではa
が存在する場合代入自体が発生しないという利点がありますが、a = a ? b
では必ず代入が発生します。 ↩