CoffeeScriptにおけるarguments.callee的な再帰の考察

題材として、以下のフィボナッチ数の計算関数を利用します。効率が悪いのはご愛嬌。

fib = (n) ->
  return 1 if n is 1 or n is 2
  (fib n-1) + (fib n-2)

alert fib 10

ここで「fib(10)の結果を取得したいが、これ以上fibを使わないため、出来ればfibを消すかローカル変数にしたい」と仮定します。

JavaScriptならarguments.calleeを用いれば

alert((function(n) {
  if (n == 1 || n == 2)
    return 1;
  arguments.callee(n-1) + arguments.callee(n-2);
})(10));

このようにarguments.calleeを用いてfibすら宣言せずに書けます。

ちなみにStrictモードではarguments.calleeが非推奨なので「名前付き無名関数」を使うのが正攻法です。

alert((function fib(n) {
  if (n == 1 || n == 2)
    return 1;
  fib(n-1) + fib(n-2);
})(10));

ということで、CoffeeScriptでもこんな感じで書こう、という試みです。

普通の解決策としては、素直に無名関数で囲う方法があります。

alert do ->
  fib = (n) ->
    return 1 if n is 1 or n is 2
    (fib n-1) + (fib n-2)

  fib 10

もうちょっとスマートにすると

alert do ->
  (fib = (n) ->
    return 1 if n is 1 or n is 2
    (fib n-1) + (fib n-2)) 10

という感じで書けますが、CoffeeScriptだとカッコで囲むのは何だかキモいですね。

そこで、doに渡す関数に、名前をつけ、さらにデフォルト引数を利用するのが今回の提案です。

alert do -> do fib = (n=10) ->
  return 1 if n is 1 or n is 2
  (fib n-1) + (fib n-2)

do -> doの部分が若干キモいですが、かなり自然になったのではないかと思います。

ちなみに、変換すると以下のコードが生成されます。

alert((function() {
  var fib;
  return (fib = function(n) {
    if (n === 1 || n === 2) {
      return 1;
    }
    return (fib(n - 1)) + (fib(n - 2));
  })(10);
})());

doのデフォルト引数が無名関数の引数になるのは(例えばdo (n=10) ->(function(n) {})(10);)、マイナーながら使えるテクかもしれません。