使用 Lambda 表達式編寫遞歸三:實現 Y 組合子


本系列文章目錄:

 

也許你我都難以理解,為什么有人對她痴迷瘋狂,銘記在心中不說,還要刻在身上:

y_combinator

她讓人絞盡腦汁,也琢磨不定!她讓人心力憔悴,又百般回味!

她,看似平淡,卻深藏玄機!她,貌不驚人,卻天下無敵!

她是誰?她就是 Y 組合子Y = λf.(λx.f (x x)) (λx.f (x x)),不動點組合子中最著名的一個。

 

小小開場抒情后,開始本文的內容,使用 c# 實現 Y 組合子。

本文繼續使用上一篇文章中的類型假定,假定遞歸函數:

  • 參數為 int;
  • 返回值為 long。

實現 Y 組合子算法

Y 組合子 η-展開

Y 組合算子的定義:

1
Y = λf.(λx.f(x x)) (λx.f(x x))

根據前文所述,對於傳值調用,應使用 η-展開后的組合子:

1
Y = λf.(λx.f(λv.((x x)v))) (λx.f(λv.((x x)v)))   

這個也稱為 Z 組合子,是將  (x x) η-展開為  λv. ((x x) n))

不過我更傾向於另外一種 η-展開形式:即將 Y 組合子中的  f (x x)  η-展開為  λn. (f (x x) n)) ,最終得出:

1
Y = λf.(λx.λn.(f(x x)n))) (λx.λn.(f(x x)n)))

(不知道這個組合子有沒有名字,沒有的話就姑且稱為鶴沖天組合子吧,呵呵!)

進行一次 α-變換 變換(將參數 f 改成 g),得出:

1
Y = λg.(λx.λn.(g (x x) n))) (λx.λn.(g(x x)n)))

簡化 Y 組合子

仔細觀察展開后的 Y,會發現:

1
Y = λg.(λx.λn.(g(x x)n))) (λx.λn.(g(x x)n)))

右側兩個高亮部分是相同的,定義一個中間的變量 h:

1
h = λx.λn.(g(x x)n) 

則 Y 可簡化為:

1
2
Y = λg.h h 
Y = λg.h(h)

Y 可表示為 Y = g => h(h)。

h 類型確定及實現

先來確定 h 的類型。

Y = λg. h(h) 變換一步得 Y g = h(h),再變換一次 Y(g) = h(h),可以看出 h 的返回值即是 Y 的返回值。

前文中已確定 Y 返回值的類型為 Func<int, long>,所以 h 的返回值類型為 Func<int, long>

再來確定 h 的參數類型,h 調用自身 h(h),因此 h 參數的類型就是 h 的類型,h 參數和 h 是同一類型,都是未確定的。

通過以下這個奇妙的委托,可以來表示自身調用有邏輯:

1
delegate TResult SelfApplicable<TResult>(SelfApplicable<TResult> self);

借助 SelfApplicable<TResult> ,h 類型可表示為 SelfApplicable<Func<int, long>>

根據 h 的定義

1
2
h = λx.λn.(g(x x)n) 
h = λx.λn.(g(x(x))(n)) 

由本系列第一篇文章中總結出的小規律,可寫出 lambda表達式如下:

1
SelfApplicable<Func<int, long>> h = x => n => g(x(x))(n);

說明:如果前面 Y 沒有展開的話,你得出將是:

1

     
     
     
             
      
      
      
              SelfApplicable<
      
      
      
              Func<
      
      
      
              int, 
      
      
      
              long>> h = x => g(x(x));
     
     
     
             

這樣最終得出的 Y 可以編譯通過,但運行時會出現死循環,最終導致 StackOverflowException。

Y 組合子實現

綜合以上兩小節的結論:

1
2
Y = λg.h(h)
SelfApplicable<Func<int, long>> h = x => n => g(x(x))(n);

可寫出 Y組合子的實現代碼:

1
2
3
4
Func<Func<Func<int, long>, Func<int, long>>, Func<int, long>> Y = g => {
    SelfApplicable<Func<int, long>> h = x => n => g(x(x))(n);
    return h(h);
};

與之等同的方法為:

1
2
3
4
public static Func<int, long> Y(Func<Func<int, long>, Func<int, long>> g) {
    SelfApplicable<Func<int, long>> h = x => n => g(x(x))(n);
    return h(h);
}

Y 組合子已實現,下面我們寫出幾個常用音頻函數。

單步函數

根據上文確定出的 g 類型,可以寫出:

階乘的單步函數:

1
Func<Func<int, long>, Func<int, long>> g = f => n => n == 0 ? 1 : n * f(n - 1);

斐波那契數列求值單步函數:

1
Func<Func<int, long>, Func<int, long>> g = f => n => n < 2 ? n : f(n - 1) + f(n - 2);

進行階乘計算

綜合以上代碼:

1
2
3
4
5
6
7
8
9
10
delegate TResult SelfApplicable<TResult>(SelfApplicable<TResult> self);

static void Main(string[] args) {
    Func<Func<Func<int, long>, Func<int, long>>, Func<int, long>> Y = g => {
        SelfApplicable<Func<int, long>> h = x => n => g(x(x))(n);
        return h(h);
    };
    Func<int, long> fact = Y(f => n => n == 0 ? 1 : n * f(n - 1));
    long result = fact(5);  // 結果為 120;
}

對 Y 組合子進行封裝

考慮到 Y 的復雜度及重用,可以把相關代碼封裝成如下:

1
2
3
4
5
6
7
public static class YCombitator {
    delegate TResult SelfApplicable<TResult>(SelfApplicable<TResult> self);
    public static Func<TInput, TResult> Fix<TInput, TResult>(Func<Func<TInput, TResult>, Func<TInput, TResult>> g) {
        SelfApplicable<Func<TInput, TResult>> h = x => n => g(x(x))(n);
        return h(h);
    }
}

使用時就方便多了:

1
2
3
4
5
var factorial = YCombitator.Fix<int, int>(f => n => n == 0 ? 1 : n * f(n - 1));
var result1 = factorial(5);   // 120

var fibonacci = YCombitator.Fix<int, int>(f => n => n < 2 ? n : f(n - 1) + f(n - 2));
var result2 = fibonacci(5);   // 5

總結

通過本文及前面兩篇文章的努力,終於使用 c# 實現 Y 組合子,達到了使用 lambda 構造遞歸函數的目的。

不過,本系列還沒有結束:在msdn一篇文章中,我看到這樣一種寫法:

1
2
3
4
5
6
7
8
9
10
public class Program{
  delegate T SelfApplicable<T>(SelfApplicable<T> self); 
  static void Main(string[] args)  {
     SelfApplicable<Func<Func<Func<int,int>, Func<int,int>>, Func<int,int>>> Y = y => f => x => f(y(y)(f))(x); 
     Func<Func<Func<int, int>, Func<int, int>>, Func<int, int>> Fix = Y(Y); 
     Func<Func<int,int>, Func<int,int>> F = fac => x => x == 0 ? 1 : x * fac(x-1); 
     Func<int,int> factorial = Fix(F);
     var result = factorial(5);   // 120 
   }
}

看上去比本文中的實現要簡單方便,怎么得出的?我會在下一篇文章中告訴大家,敬請期待!

 

附:相關資源:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM