無名再帰関数

再帰を無名のままやりたい。

無名の自分をどうやって呼ぶんだ?
名前を付けてやればいい。
無名で名前が付けられるのはというと、仮引数しかない。
何かの関数の実引数としてわたしてやればいいんじゃない?
こんな感じ

def nanika(f)
  f	# あれ?
end

引数としてfが渡されてきたということは、fは定義済みでブラックボックス
fの定義をしたいのに。

fにブロックを渡せるようにしておけばいいんじゃない?
で、fの定義でブロックを呼び出す。
つまり、fの定義(の一部)を実行時まで遅らせる、実行時に決定できるようにしておく。

def f(self, x)
  ...
  self(self, x-1)
  ...
end

こんな感じ。仮引数のselfは、自分自身(f)が入ってくる予定だよ〜という気持ち。
そしたら、nanikaの中では

def nanika(f, x)
  f(f, x)
end

としておけばいい。
たとえば、階乗だと、

def fact(self, x)
  if x == 0
    1
  else
    x * self(self, x-1)
  end
end

などとしておく。
そして、nanikaにfを突っ込む。

nanika(fact, 2)

ばらしていくと、

nanika(fact, 2)
↓
fact(fact, 2)
↓
#  if 2 == 0
#    1
#  else
#    2 * fact(fact, 2-1)
#  end
↓
2 * fact(fact, 1)
↓
fact(fact, 1)
↓
# if 1 == 0
#   1
# else
#   1 * fact(fact, 1-1)
# end
↓
fact(fact, 0)
↓
# if 0 == 0
#   1
# else
#   (略)
# end
↓
1
↓
1 * 1
↓
2 * 1 * 1
=> 2

めでたし。
nanikaもfactも見た目上、再帰で書かれていないから、無名にするのは簡単。

nanika
↓
Proc.new{|f, x|
  f(f, x)
}

fact
↓
Proc.new{|self, n|
  if n == 0
    1
  else
    self(self, n-1)
  end
}

これをつなげればいい。
無名で自分を呼べました。

上に書いたコードや用語は適当です。下のコードは動くと思います。

# Anonymous
fact10 =
Proc.new{|f|
  Proc.new{|n|
    f.call(f, n)
  }
}.call(
Proc.new {|slf, m| 	# factorial
    if m == 0
      1
    else
      m * slf.call(slf, m-1)
    end
}
).call(10)

p fact10