使用 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. 

右侧两个高亮部分是相同的,定义一个中间的变量 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 <TResult>(<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
<<int, long>> h = x => n => g(x(x))(n);

说明:如果前面 Y 没有展开的话,你得出将是:

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

这样最终得出的 Y 可以编译通过,但运行时会出现死循环,最终导致 StackOverflowException。

Y 组合子实现

综合以上两小节的结论:

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

可写出 Y组合子的实现代码:

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

与之等同的方法为:

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

Y 组合子已实现,下面我们写出几个常用音频函数。

单步函数

根据上文确定出的 g 类型,可以写出:

阶乘的单步函数:

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

斐波那契数列求值单步函数:

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

进行阶乘计算

综合以上代码:

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

static void Main(string[] args) {
    <<<int, long>, <int, long>>, <int, long>> Y = g => {
        <<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  {
    delegate TResult <TResult>(SelfApplicable<TResult> self);
    public static <TInput, TResult> Fix<TInput, TResult>(<<TInput, TResult>, <TInput, TResult>> g) {
        <<TInput, TResult>> h = x => n => g(x(x))(n);
        return h(h);
    }
}

使用时就方便多了:

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

var fibonacci = .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 <T>(SelfApplicable<T> self); 
  static void Main(string[] args)  {
     <<<<int,int>, <int,int>>, <int,int>>> Y = y => f => x => f(y(y)(f))(x); 
     <<<int, int>, <int, int>>, <int, int>> Fix = Y(Y); 
     <<int,int>, <int,int>> F = fac => x => x == 0 ? 1 : x * fac(x-1); 
     <int,int> factorial = Fix(F);
     var result = factorial(5);   // 120 
   }
}

看上去比本文中的实现要简单方便,怎么得出的?我会在下一篇文章中告诉大家,敬请期待!

 

附:相关资源:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值