litediary | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
過去の記事
2015年04月
2015年01月
2014年09月
2014年07月
2014年02月
2013年12月
2013年01月
2012年12月
2012年11月
2012年07月
2012年06月
2012年05月
2012年04月
2012年03月
2012年02月
2012年01月
2011年12月
2011年11月
2011年10月
2011年08月
2011年07月
2011年06月
2011年05月
2011年04月
2011年03月
2011年01月
2010年12月
2010年11月
2010年10月
2010年09月
2010年08月
2010年07月
2010年06月
2010年04月
2010年03月
2010年02月
2010年01月
2009年12月
2009年11月
2009年10月
2009年09月
2009年08月
2009年07月
2009年06月
2009年05月
2009年04月
2009年03月
2009年02月
2009年01月
2008年12月
2008年11月
2008年10月
2008年09月
2008年08月
2008年07月
2008年06月
2008年05月
2008年04月
2008年03月
2008年02月
2008年01月
2007年12月
2007年11月
2007年10月
2007年09月
2007年08月
2007年07月
2007年06月
2007年05月
2007年04月
2007年03月
2007年02月
2006年12月
2006年11月
2006年10月
2006年09月
2006年08月
2006年07月
2006年06月
2006年05月
2006年04月
2006年03月
2006年02月
2006年01月
2005年12月
2005年11月
2005年10月
2005年09月
2005年08月
2005年07月
2005年06月
2005年05月
2005年04月
2005年03月
2005年02月
2005年01月
2004年12月
2004年11月
2004年10月
2004年09月
2004年08月
2004年07月
2004年06月
2004年05月
2004年04月
2004年03月
2004年02月
|
Yコンビネータが理解できない09/08/06 22:53
今日はC#。 ツリービューにファイルツリーを表示するのを、 λ式で再帰的に書こうとしてみた。 Action<string, TreeNodeCollection> func = null; func = (path, nodes) => { TreeNode node = nodes.Add(Path.GetFileName(path)); node.Tag = path; if (Directory.Exists(path)) { Array.ForEach( Directory.GetDirectories(path).Concat(Directory.GetFiles(path)).ToArray(), child => func(child, node.Nodes)); } }; func(@"C:", treeView1.Nodes); 1行目、一度funcにnullを入れて、2行目でλ式を入れている。 こうしないと、func = の右辺ではまだfuncが未初期化なので再帰しようとしているところでコンパイルエラー。 ていうかかっこ悪い。 スマートにかけないのかと調べていると、Yコンビネータなるものにたどり着いてしまう。 よく分からないのだが、これを使うと再帰が綺麗にかけるらしい。 ぐぐった結果、こんな答えが出てきた。 delegate Func<A, R> Recursive<A, R>( Recursive<A, R> f ); Func<Func<Func<int, int>, Func<int, int>>, Func<int, int>> y = f => ( (Recursive<int, int>)( g => a => f( g( g ) )( a ) ) ) ( (Recursive<int, int>)( g => a => f( g( g ) )( a ) ) ); var int120 = y( fact => n => n == 0 ? 1 : n * fact( n - 1 ) )( 5 ); 5の階乗を再帰で求めている。 なるほど、確かにこれなら綺麗に書けるみたいだけど… 全く意味が分からない。 こんな感じのメソッド作っておけば… Func<A, R> Y<A, R>(Func<Func<A, R>, Func<A, R>> func) { Func<Func<Func<A, R>, Func<A, R>>, Func<A, R>> y = f => ((Recursive<Func<A, R>>)(g => a => f(g(g))(a))) ((Recursive<Func<A, R>>)(g => a => f(g(g))(a))); return y(func); } var func = Y<int, int>( fact => n => n == 0 ? 1 : n * fact( n - 1 ) ); func(5); // 120 もうちょっと簡略化して、 Func<A, R> Y<A, R>(Func<Func<A, R>, Func<A, R>> f) { Recursive<Func<A, R>> rec = g => a => f(g(g))(a); return rec(rec); } うん。これなら実用できそう。 でもやっぱり理解不能。 というわけで、この記事を書いてみている。 ■シンプルな関数で考えてみる。 0...3の和を計算する。 Y<int, int>( func => arg => arg>0 ? arg + func(arg-1) : 0 )(3); これを展開をしてみよう f = { func => arg => arg>0 ? arg + func(arg-1) : 0 } rec = g => a => f(g(g))(a); rec(rec)(3); fを展開 rec = { g => a => { arg => arg>0 ? arg + g(g)(arg-1) : 0 }(a) }; rec(rec)(3); recを展開 { g => a => { arg => arg>0 ? arg + g(g)(arg-1) : 0 }(a) }({ g => a => { arg => arg>0 ? arg + g(g)(arg-1) : 0 }(a) })(3) --- (1.1) さて、この形がこれから何度も出てくるので func = { g => a => { arg => arg>0 ? arg + g(g)(arg-1) : 0 }(a) }({ g => a => { arg => arg>0 ? arg + g(g)(arg-1) : 0 }(a) }); と置こう。最初のfuncとは違うけど、同じです。 funcは展開すると { a => { arg => arg>0 ? arg + func(arg-1) : 0 }(a) } --- (1.2) となる。 見ての通り再帰になっている。 funcを用いると式(1.1)は func(3) 式(1.2)より { a => { arg => arg>0 ? arg + func(arg-1) : 0 }(a) }(3) aを3に展開 { arg => arg>0 ? arg + func(arg-1) : 0 }(3) argを3に展開 3>0 ? 3 + func(3-1) : 0 3項演算子の結果が確定するので取り出す。 3 + func(2) もう一度funcを展開すると… 3 + { a => { arg => arg>0 ? arg + func(arg-1) : 0 }(a) }(2) 後半部分がさっき出てきた形になっているので、以下同様に 3 + { arg => arg>0 ? arg + func(arg-1) : 0 }(2) 3 + 2 + { arg => arg>0 ? arg + func(arg-1) : 0 }(1) 3 + 2 + 1 + { arg => arg>0 ? arg + func(arg-1) : 0 }(0) argが0に展開され、0>0==falseとなるので 3 + 2 + 1 + 0 めでたしめでたし。 ■この証明を一般化してみよう。 Yに渡すfは一般に次のような形になる。 f = func => arg => F(func, arg) (上記の例ならF = (func, arg) => arg>0 ? arg + func(arg-1) : 0 ) Y(f)(arg)を展開していこう。 fを展開 Y({ func => arg => F(func, arg) })(arg); Yを展開 rec = g => a => { func => arg => F(func, arg) }(g(g))(a); rec(rec)(arg); funcを展開 rec = g => a => { arg => F(g(g), arg) }(a) rec(rec)(arg); argを展開 rec = g => a => F(g(g), a); rec(rec)(arg); recを展開 { g => a => F(g(g), a) }({ g => a => F(g(g), a) })(arg); --- (2.1) gを展開 { a => F({ g => a => F(g(g), a) }({ g => a => F(g(g), a) }), a) }(arg); aを展開 F({ g => a => F(g(g), a) }({ g => a => F(g(g), a) }), arg); --- (2.2) ここで、 func = { g => a => F(g(g), a) }({ g => a => F(g(g), a) }); とおくと、 式(2.1)は次のようになる。 func(arg); 式(2.2)は次のようになる。 { a => F(func, a) }(arg); argを展開 F(func, arg); Fでは、次のような呼び出しを行うだろう。 func(arg_1) するとこれは、次のように展開される。 F(func, arg_1); そして再帰的にFが呼ばれることになる。 F(func, arg_n); 再帰の終了となるarg_Nが渡された時点で、Fの呼び出しが終了し、展開も終了する。 このfuncがY<A, R>()の返す式の正体だ。 ということで良いのかな。 参考: C#λ式で再帰をやろうとしてYコンビネータに行き着くパターン(今回の自分と同じ) http://blogs.wankuma.com/mnow/archive/2008/02/17/123545.aspx http://blogs.msdn.com/wesdyer/archive/2007/02/02/anonymous-recursion-in-c.aspx Yコンビネータそのものの解説 http://d.hatena.ne.jp/nowokay/20090409 minibbs - テスト稼動中
creeper2012/05/03 01:28 .jpからもきてるんか…
creeper2012/05/02 10:52 誰も書き込まないのにSPAMばっかりくるようになったのでhost名制限かけました。 基本的に逆引きできて*.jpにならないと弾きます
wataru2010/12/01 11:43 GT5俺がかわりにやっといてあげますよ。
q2010/12/01 10:35 消化不良すぎ
2010/10/30 01:03 netbeansだとmakeファイル使う機会があるかもしれませんね
2010/10/05 06:04 なに書き込み不可能とか直しちゃってるのこわい
creeper2010/10/04 02:58 長いことコメント書き込み不可能になってた。直しました。
ao 2007/09/30 10:54 お、良いねぃ
creeper2007/09/16 05:18 そこまで詳しくなかったので調べてみた。Wikipedia万歳 なるほど、失敗する6号機まではかぐやと同じ3t強をSBB無しの202で上げてたんですね。 SRB-Aの出力を下げてSBB追加することでとりあえず対応していたと。 今後の予定をみると、次の燃えるのは2010年の金星探査機かー 「超高速インターネット衛星WINDS」は名前には惹かれるけど都心に引きこもってる限り関係ないなー Wikipediaの予定だとWINDSは2024になってますね。 重量的に、2024か204か新型か…
A責ダッシュ!2007/09/15 10:11 SSB使っての打ち上げって今回が最後じゃなかったかな? まー、今回のもSRB-Aがもともとの性能を出せてれば2022じゃなくて202で上げられたかもしれないです。LE-7Aの再生長ノズルはこなれてきたようなので、元のSRB-Aが復帰すればまたちょっとづつ色々変わっていくんじゃないのでしょうか。H-IIBも控えてますしね。
creeper2007/09/12 05:55 http://lovelove.rabi-en-rose.net/blog.php?n=256 今回はcygwinのgccつかってます。 まぁ、ソース見ての通りこの問題が1:1になるのは自明で、 かつrandの結果は偶数奇数が交互に出るようなこともなく、 おかしな偏りも無かったのでOKということで。 |