?= と or= を CoffeeScript 1.2.0 で調べてみる

CoffeeScriptRuby に似ているところがたくさんあり,Ruby プログラマにも比較的馴染みやすい言語だと思います.たとえば,変数を初期化するのに便利な Ruby||= に対して,CoffeeScript にも予想通り or= があります.しかし,CoffeeScript (というか JavaScript)では nullundefined だけでなく 0"" (空文字列)も false 扱いなので困ることがあります.そんなときのために,or= のほかに ?= が用意されています.?CoffeeScript の存在確認演算子で,undefinednull のときにのみ false になります.これで,0""true 扱いできます.すなわち,

undefined?   #=> false
null? #=> false
0? #=> true
''? #=> true

となります.ここで a or= ba ?= b とは,それぞれ,

a or a = b
a ? a = b

と同じだと思っておいて間違いないだろう,というのがこのエントリの結論です.

本文では,or=?= を含んだ式を CoffeeScript 1.2.0 でコンパイルしてみて JavaScript でどうなっているかを確認していきますが,時間が無い人は上の結論だけで十分です.

はじめに

Tumblr?= と or= の違いを理解して,より CoffeeScript っぽいコードを書く というテキストをポストしたら,


a ?= ba or= b は,)正確にはそれぞれ

  • a ? a = b
  • a || a = b

の略です。

とコメントをもらいました.実は元 Tumblr のほうに「a ?= ba = b unless a? の短縮形」とか,「a or= ba = a or b の短縮形」と書いていたんです.このコメントが無ければ,それ以上考えることは無かったので,ありがたいコメントでした.

コメントをもらって,何が正確か調べてみようと最初は考えたのですが,http://coffeescript.org/ に行っても厳密な言語リファレンスがあるわけでもなく,何か「正確」が書いてある文書も無さそうなので,CoffeeScript 1.2.0 がどのように JavaScriptコンパイルするかを調べてみることにしました.

?= を調べてみる.

まずは,?= から調べてみます.ためしに,?= を使った例を CoffeeScript で書いてみました.

# conditional_assignment-1.coffee

a1 ?= b1 # 1)
a2 = b2 unless a2? # 2)
a3 ? a3 = b3 # 3)

これを,コンパイルしてみます.

% coffee -v
CoffeeScript version 1.2.0
% coffee -bp conditional_assignments-1.coffee
var a2, a3;

if (typeof a1 === "undefined" || a1 === null) a1 = b1;

if (typeof a2 === "undefined" || a2 === null) a2 = b2;

if (typeof a3 !== "undefined" && a3 !== null) {
a3;
} else {
a3 = b3;
};

実行結果は例 1)〜3) で変わらないものの,JavaScript は例 1) a1 ?= b1 と例 2) a2 = b2 unless a2? が同じ結果になりました.しかし,これだけで a ?= ba = b unless a? と同じと言い切ってはいけません.なぜなら,a?false になるときの戻り値が a ?= ba = b unless a? とで異なるからです.次に,戻り値を考慮した例を書いてみます.

# conditional_assignment-2.coffee

c = (a4 ?= b4) # 4)
c = (a5 = b5 unless a5?) # 5)
c = (a6 ? a6 = b6) # 6)

これを,コンパイルすると先程と様子が違います.

% coffee -bp conditional_assignments-2.coffee 
var a5, a6, c;

c = (typeof a4 !== "undefined" && a4 !== null ? a4 : a4 = b4);

c = (typeof a5 === "undefined" || a5 === null ? a5 = b5 : void 0);

c = typeof a6 !== "undefined" && a6 !== null ? a6 : a6 = b6;

今度は例 4) と例 6) が同じ結果を返すようになりました.ただし,なぜ例 6) のほうに括弧が無いのかは謎です.話を戻すと,例 5) は a5? が設定済みのときに void 0 すなわち undefined を返すようになっています.これに対して,例 4) と例 6) は,undefined にはなりません.

まとめると,a ?= b は,生成される JavaScript だけを見ると a = b unless a? と同じになることがあるが,戻り値を考慮するなら a ? a = b と同じ結果になるということです.

or= を調べてみる

こちらのほうも調べてみます.まず,CoffeeScript の例をつくって,

# conditional_assignments-3.coffee

a1 or= b1 # 1)
a2 = a2 or b2 # 2)
a3 or a3 = b3 # 3)

c = (a4 or= b4) # 4)
c = (a5 = a5 or b5) # 5)
c = (a6 or a6 = b6) # 6)

コンパイルしてみます.

% coffee -bp conditional_assignments-3.coffee 
var a2, a3, a5, a6, c;

a1 || (a1 = b1);

a2 = a2 || b2;

a3 || (a3 = b3);

c = (a4 || (a4 = b4));

c = (a5 = a5 || b5);

c = a6 || (a6 = b6);

こちらも括弧の有無はあるものの,「a or= ba or a = b」と言って差し支えありません.a or= b では a が真のときには代入が発生しないのに対し,a = a or b では a が真でも代入が発生するのが違います.

おわりに

CoffeeScript で便利な ?=or= について調べてみました.コンパイル後の JavaScript に若干の違いがありますが,それぞれ,a ? a = ba or a = b だと思っておいて間違いありません.a ?= ba = b unless a? とは戻り値が異ります.a or= ba = a or b は代入が発生するかどうかで違いがあります.以上,CoffeeScript 1.2.0 での話でした.

今回は Tumblr へのコメントで勉強させてもらいました.コメントをくださった id:murky-satyr に感謝します.