答:關於Fix的問題你fix了嗎?html
問:慢着,讓我想一想,上次留下個什麼問題來着?是說咱們有了一個求不動點的函數Fix,但Fix倒是顯式遞歸的,是吧?函數
答:有勞你還記的這個問題。spa
問:Fix的參與背離了匿名遞歸的定義,因此…因此…咱們被Fix給坑了?code
答:固然不是。你還記的第(一)章咱們討論過什麼嗎?htm
問:記的,咱們把一個顯式遞歸的Fact變成了一個匿名遞歸的結構。blog
答:很好,讓咱們再造一次輪子。遞歸
問:哦!我明白了,是用與上次相似的方法,把Fix寫成一個匿名遞歸的Lambda。ip
答:就是這個意思,如此即可用這個Lambda來得到不動點了。那就動手吧,讓咱們一路與Fact對比:get
//C# //Func3 Fix = f=>f(Fix(x=>f(x))) //寫成函數的形式爲: Func1 Fix(Func2 f) { return (Func1)(f(x =>Fix(f)(x))); } //對比Fact int Fact(int x) { return x == 0 ? 1 : x * Fact(x - 1); }
//C# //爲了把本身傳給本身構造fix_maker OuroborosFunc<Func3> fix_maker = self => f => f(x => self(self)(f)(x)); //對比fact_maker OuroborosFunc<Func<int, int>> fact_maker = self => x => x == 0 ? 1 : x * self(self)(x - 1);
//C# //自我調用產生Fix Func3 Fix = ((Func<OuroborosFunc<Func3>, Func3>)(s => s(s))) (self => f => f(x => self(self)(f)(x))); //對比自我調用產生的Fact Func<int, int> Fact = ((Func<OuroborosFunc<Func<int, int>>, Func<int, int>>)(s => s(s))) (self => x => x == 0 ? 1 : x * self(self)(x - 1));
觀察最後產生的Fix,發現咱們已經擺脫了顯式的遞歸調用,且獲得了一個可複用的不動點「生成器」。這裏插一句題外話,我以前按照上述思路獲得了Fix,後來才發現,原來這就是阿蘭•圖靈當年發現的Θ組合子(固然,我獲得這個組合子要容易得多,由於圖靈的年代根本沒有計算機,更別說程序語言了)。我相信這兩個組合子是徹底等價的,下一章咱們將以非形式化的方法來理解這一點。it
問:那麼,是時候揭開Y組合子的面紗了吧。
答:讓子彈再飛一下子。在此以前,「插播」一個小問題:若是遞歸函數的輸入參數不止一個,該怎麼辦呢?例如用展轉相除法求最大公約數:
//C# //GCD的顯式遞歸 int GCD(int m, int n) { return n == 0 ? m : GCD(n, m % n); }
問:讓我想一想…嗯…那就要對以前定義的Y<T,TResult>作一些改變,寫成這樣:
//C# class Y<T1, T2, TResult> { public delegate TResult Func1(T1 p1, T2 p2); public delegate Func1 Func2(Func1 f1); public delegate Func1 Func3(Func2 f2); ... ... }
而後,對以前的Fix改寫成這樣:
//C# Func3 Fix = ((Func<OuroborosFunc<Func3>, Func3>)(s => s(s))) (self => f => f((x, y) => self(self)(f)(x, y)));
如此即可用這個Fix獲得gcd_seed的不動點,即GCD遞歸函數:
//C# Y<int, int, int>.Func2 gcd_seed = gcd => (m, n) => n == 0 ? m : gcd(n, m % n); Console.WriteLine(Y<int, int, int>.Fix(gcd_seed)(24, 15)); //3
答:很好,這是一個方案,但不是一個通用方案。若是有三個輸入參數的遞歸呢?四個呢?…
問:難道還有更好的方法嗎?
答:是的,答案就是Currying(柯里化)。所謂柯里化,就是把接受多個參數的函數變換成接受單一參數的函數,而且返回以柯里化的形式接受剩餘參數,最終返回結果的一種技術。(注:我是在獨立想到解決方案以後,才知道Currying的存在的哦!)
問:這…講得有點抽象…
答:那就直接上例子吧。在上述最大公約數的例子中,把GCD柯里化成以下形式:
//C# Func<int, int> GCD(int m) { return n => n == 0 ? m : GCD(n)(m % n); }
即:高階函數GCD以m爲參數,返回一個能與m求最大公約數的函數,這個函數再受一個參數n,即可返回(m,n)的最大公約數。同時,Y<T, TResult>和Fix無需任何改變,便可完成有兩個輸入參數的遞歸:
//C# Y<int, Func<int, int>>.Func2 gcd_seed = gcd => m => n => n == 0 ? m : gcd(n)(m % n); Console.WriteLine(Y<int, Func<int, int>>.Fix(gcd_seed)(24)(15)); //3
依此看來,兩個參數的Y<T, TResult>就足以知足任何遞歸的需求了,美哉!
問:然是美哉!
答:就在下章,靜候Y組合子的「粉墨登場」。待續…