Yコンビネータによる再帰 のバックアップの現在との差分(No.1)

更新


  • 追加された行はこの色です。
  • 削除された行はこの色です。
[[公開メモ]]

* Yコンビネータによる再起 [#b06742b7]
#contents

* Yコンビネータによる再帰 [#b06742b7]

http://qiita.com/mrpepper/items/6a538ef0b2d11fc311ac

こちらの記事を読んで、Yコンビネータによる再起の方法を知りました。
こちらの記事を読んで、Yコンビネータによる再帰の方法を知りました。

が、一見しただけだとどんな仕組みで動作しているのかが分からなかったので、勉強してみました。

ここではYコンビネータの数学的な話などは全部置いておいて、
コードとしてなぜこれが動くのか、を理解したいと思います。

言語としては Coffee Script を使っています。

http://coffeescript.org/ こちらの一番上で
TRY COFFEESCRIPT にコードを貼って、右上の run で走らせることができます。

* まずは通常の再起処理コード [#wfe3029b]
* まずは通常の再帰処理コード [#wfe3029b]

恐らくもっとも有名な、階乗計算のコードはこんな感じ。

 LANG:coffeescript
 factorial = (n)->
     if n == 1
       return 1
     else
       return factorial(n-1) * n
       return n * factorial(n-1)
 
 alert factorial(4) 
 # displays 4! == 24

1の階乗は1。

nの階乗はn-1の階乗にnを書けた物。
nの階乗はn-1の階乗にnを掛けた物。

という素直な実装です。

* Yコンビネータを使ったコード [#t955c007]

上記サイトのコードを少し変更して、以下のような物を試してみたところ、
驚くべきことに(!) 正しく 24 が表示されました。

 LANG:coffeescript(linenumber)
 y_combi = (func)->
   return ((p)->
     return (n)->
       return func(p(p))(n)
   )((p)->
     return (n)->
       return func(p(p))(n)
   )
 
 factorial_y = y_combi (reccurse)->
   return (n)->
     if n == 1
       return 1
     else
       return reccurse(n-1) * n
       return n * reccurse(n-1)
 
 alert factorial_y(4) 
 # displays 4! == 24

* factorial_y を精査する [#e11d7034]

factorial_y は reccurse という関数を受け取って、
「引数 n を取る関数」を返す形に直されています。
「引数 n を取る関数」を返す関数に y_combi を適用した形に直されています。

そして、再帰呼び出しをする

       return factorial(n-1) * n
 LANG:coffeescript
       return n * factorial(n-1)

が、仮引数で渡される reccurse に置き換えられて

       return reccurse(n-1) * n
 LANG:coffeescript
       return n * reccurse(n-1)

となっています。

あたかも reccurse の呼び出しが自分自身を再帰呼び出しするような関数を
外部から与えられると期待しているようですね。
reccurse を呼び出すことで自分自身を再帰呼び出しできるような
引数が外部から与えられると期待しているようですね。

* y_combi を精査する [#ubf885df]

1行目から分かるように、y_combi は func を渡すと何かを返す関数です。

func には factorial_y のような関数が与えられます。
func には factorial_y の (reccurse)-> 以下のような関数が与えられます。

何が返されるか、は、かなり複雑なのですが、y_combi の中身は
y_combi から何が返されるか、を読むのは、なかなか骨が折れますね。

y_combi の中身は

 LANG:coffeescript(linenumber)
 y_combi = (func)->
   return ((p)->
     ...
 
   )((p)->
     ...
 
   )

という形になっていますので、2行目の (p)-> から始まる関数に、
5行目の (p)-> から始まる関数を引数として与えた結果を返すと読めます。

2つの ... の内容は等しいので、2行目の (p)-> から始まる関数と、
5行目の (p)-> から始まる関数の内容は等しいです。

ですので、こう書き直しても同じ動作をします。

 LANG:coffeescript(linenumber)
 LANG:coffeescript
 y_combi = (func)->
   core = (core)-> (n)-> func(core(core))(n)
   return core(core)

関数型変数の core と、λ式の仮引数の core の名前をわざと同じにしたせいで、
スコープがややこしいかもしれませんが、実際には引数にも core 自身が渡されるため、
結果的に core と書かれた部分にはすべて等しい値が入ります。

そして、factorial_y の y_combi に続く (reccurse)-> 以下が
y_combi の func に対応することを考えると、
reccurse の中身は core(core) であることになります。
func に core(core) を与えて呼び出していますので、
factorial_y の中の reccurse には core(core) が入ることになります。

core(core) は (n)-> func(core(core))(n) を返すので、

func(core(core))(n) の中で呼ばれる
reccurse(n-1) は core(core)(n-1) のことで、
これは func(core(core))(n-1) を返します。

func(core(core))(n-1) の中で呼ばれる
reccurse(n-2) は core(core)(n-2) のことで、
これは func(core(core))(n-2) を返します。

func(core(core))(n-2) の中で呼ばれる
reccurse(n-3) は core(core)(n-3) のことで、
これは func(core(core))(n-3) を返します。

という具合で、確かに正しく求まりそうなことがようやく理解できました。


* 実行効率 [#p67a6ebe]

これ、確かに動きそうですが、実行効率はどうでしょう。

factorial が n 重の関数再帰呼び出しで計算可能です。

Yコンビネータを使った方では、func, core が適切に設定されている前提では

 LANG:coffeescript
 factorial_y = (n)-> func(core(core))(n)

ですので、見た目の複雑さに反してこちらも n 回の再帰で済みますが、
同時に core(core) の計算を n 回行っている分だけ余計な計算が必要です。

思ったほどは効率に影響がないので、末尾再帰の最適化などが実装されると、
ばりばり使っても問題ないのかもしれません?

* Y コンビネータの代わりにこれじゃダメなのかしら? [#t89532f4]

 LANG:coffeescript
 y_combi = (func) ->
   caller= (n) -> func(caller)(n)

以下のようにすれば任意個数の引数にも対応できます。

 LANG:coffeescript
 y_combi = (func) ->
   caller= (args...) -> func(caller)(args...)

正しく動きそうなことを以下で確認できます。

http://jsbin.com/hudaziyusa/edit?js,console

下記にわかりやすい記事を発見しました。

http://d.hatena.ne.jp/amachang/20080124/1201199469

たぶん、わざわざ Y コンビネータを使うのは理論的な意味合いしかないという理解で合っていそうです。

「工夫次第では再起処理まですべてラムダ形式で書けるよね、すごいよね」ということだと思っていればよいような。

一方で、

 LANG:coffeescript
 y_combi = (func) ->
   caller= (args...) -> func(caller)(args...)

こういう実装でYコンビネータと同じ意味合いの処理を書き、それを使って非同期処理を書くことにはそれなりの意味がありそう?~
なのかなあ???
* コメント・質問 [#m79601c4]

#article_kcaptcha


Counter: 7266 (from 2010/06/03), today: 3, yesterday: 3