CoffeeScriptには「関数で最後に評価した値が戻り値になる」というルールがあります。これはどの教科書にも書いてありますので説明は省略しますが、これでいちいちreturnを書く手間を省けます。
またCoffeeScriptではforやwhileも左辺値になり、最終文の値を要素とする配列を返します。こちらは簡単な例を示します。
a = for x in [0..5]
x * x
console.log a # => [ 0, 1, 4, 9, 16, 25 ]
b = while a.length > 0
a.pop()
console.log b # => [ 25, 16, 9, 4, 1, 0 ]
さらに内包表記を使うと一行に収まりますが、演算子の優先順位には注意が必要です。
a = (x * x for x in [0..5]) # (...)が必要
console.log a # => [ 0, 1, 4, 9, 16, 25 ]
b = (a.pop() while a.length > 0) # これも(...)が必要
console.log b # => [ 25, 16, 9, 4, 1, 0 ]
ところがこれを関数の中で用いると思わぬ副作用を生じることがあります。次の例は簡単な数値配列のフォーマット出力関数です。
# sprintf("%#{w}.#{f}f", n) のCoffeeScript版
sprintf = (n, w, f) ->
buf = n.toFixed f
buf = ' ' + buf while w > buf.length
buf
printNumbers = (numbers, w, f) -> # CoffeeScriptならたった一行(なのに...)
console.log sprintf n, w, f for n in numbers
printNumbers [1.5, 7.3, -4.1], 5, 1
### 出力は次の通り
1.5
7.3
-4.1
###
これは正しく動作しますが、coffee -c printNumbers.coffee
でJavaScriptソースを生成すると無駄なコードが作られているのが分かります。
// Generated by CoffeeScript 1.7.1
(function() {
var printNumbers, sprintf;
// 中略
// 何でこんなに長くなるのか? 実は理由がある
printNumbers = function(numbers, w, f) {
var n, _i, _len, _results;
_results = []; // 値を返すための(無駄な)配列
for (_i = 0, _len = numbers.length; _i < _len; _i++) {
n = numbers[_i];
_results.push(console.log(sprintf(n, w, f)));
// console.log(...)の戻り値(undefined)を追加
}
return _results; // => [ undefined, undefined, ... ]
};
// 以下略
}).call(this);
原因はたまたまprintNumbers関数の最終文がforだったためです。forの中はconsole.log
ですがこれは値を返しません。そこでprintNumbersは[ undefined, undefined, ... ]
を生成して戻り値として返します。
undefinedだけの配列は無害ですが何の役にも立ちませんから直しましょう。最後に単独のreturnを追加すれば解決できます。
printNumbers = (numbers, w, f) ->
console.log sprintf n, w, f for n in numbers
return # <= !!! 追加 !!!
これでJavaScriptの出力もすっきりします。
// Generated by CoffeeScript 1.7.1
(function() {
// 中略
// この出力ならリーズナブル(修正不要)
printNumbers = function(numbers, w, f) {
var n, _i, _len;
for (_i = 0, _len = numbers.length; _i < _len; _i++) {
n = numbers[_i];
console.log(sprintf(n, w, f));
}
};
// 以下略
}).call(this);
しかしforやwhileが最後にならない場合も「(最終版コードには)値を返さない関数は必ず最後にreturnを付ける」ことをお勧めします。次のような簡単な場合でもCoffeeScriptは「最後に実行した結果が戻り値」というルールに従い不要なreturn文を生成しています。
log = (x) -> # console.logの短縮形
console.log x
// Generated by CoffeeScript 1.7.1
(function() {
var log;
log = function(x) {
return console.log(x); // このreturnは不要
};
}).call(this);
これを抑制するのがCoffeeScriptの単独return文です。
log = (x) ->
console.log x
return # <= 追加
// Generated by CoffeeScript 1.7.1
(function() {
var log;
log = function(x) {
console.log(x); // returnは付かない
};
}).call(this);
これは私が天気データマップを作っている最中に気付いたCoffeeScriptのちょっとした注意点です。returnを書かなくても動作は変わりませんが、これに気付いてコードを修正したところけっこうな最適化効果がありました。