我是否使用C#动态实现了Y-combinator,如果没有,那么它是什么?

时间:2011-10-05 09:11:12

标签: c# dynamic functional-programming y-combinator

我的大脑似乎处于自虐模式,所以在被thisthisthis淹死之后,它想要在C#中使用一些DIY。

我想出了以下内容,我不认为是Y-combinator,但 似乎设法使非递归函数递归,没有提到自己:

Func<Func<dynamic, dynamic>, Func<dynamic, dynamic>> Y = x => x(x);

所以给出了这些:

Func<dynamic, Func<dynamic, dynamic>> fact = 
                  self => n => n == 0 ? 1 : n * self(self)(n - 1);
Func<dynamic, Func<dynamic, dynamic>> fib = 
                  self => n => n < 2 ? n : self(self)(n-1) + self(self)(n-2);

我们可以生成这些:

Func<dynamic, dynamic> Fact = Y(fact);
Func<dynamic, dynamic> Fib = Y(fib);

Enumerable.Range(0, 10)
          .ToList()
          .ForEach(i => Console.WriteLine("Fact({0})={1}", i, Fact(i)));

Enumerable.Range(0, 10)
          .ToList()
          .ForEach(i => Console.WriteLine("Fib({0})={1}", i, Fib(i)));

1 个答案:

答案 0 :(得分:7)

不,那不是Y组合者;你只有一半在那里。您仍然需要在应用它的“种子”功能中分解自我应用程序。也就是说,而不是:

Func<dynamic, Func<dynamic, dynamic>> fact = 
                  self => n => n == 0 ? 1 : n * self(self)(n - 1);

你应该有这个:

Func<dynamic, Func<dynamic, dynamic>> fact = 
                  self => n => n == 0 ? 1 : n * self(n - 1);

注意第二个定义中单次出现self而不是第一个定义中的两次出现。

(编辑添加:)顺便说一句,因为您使用C#通过按值调用评估lambda演算,所需的定点组合器是the one often called Z, not Y

(再次编辑以详细说明:)描述Y的等式就是这个(参见the wikipedia page的推导):

Y g = g (Y g)

但在大多数实用的编程语言中,您在调用函数之前评估函数的参数。在编程语言社区中,这称为按值调用评估(不要与C / C ++ / Fortran / etc程序员区分“按值调用”与“按引用调用”与“通过复制还原调用”的方式混淆等等。)

但如果我们这样做,我们就会得到

Y g = g (Y g) = g (g (Y g)) = g (g (g (Y g))) = ...

也就是说,我们将花费所有时间构建递归函数,并且永远不会到达应用它。

另一方面,在名称调用评估中,您将函数(此处为g)应用于未评估的参数表达式,此处为(Y g)。但如果gfact类似,那么它在做任何事之前都在等待另一个参数。因此,在尝试进一步评估g之前,我们会等待(Y g)的第二个参数 - 并且取决于函数的作用(即,如果它具有基本情况),我们可能不需要完全评估(Y g)。这就是Y适用于名称评估的原因。

按值调用的修正方法是更改​​等式。而不是Y g = g (Y g),我们需要类似下面的等式:

Z g = g (λx. (Z g) x)

(我认为我的方程是正确的,或者接近正确。你可以计算出来,看看它是否符合Z的定义。)

考虑到这一点的一种方法是,不是计算“整个递归函数”并将其交给g,我们将它交给一个函数,它将一次一点地计算递归函数 - 和只有当我们真的需要更多它时才能将它应用于参数(x)。