我的大脑似乎处于自虐模式,所以在被this,this和this淹死之后,它想要在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)));
答案 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)
。但如果g
与fact
类似,那么它在做任何事之前都在等待另一个参数。因此,在尝试进一步评估g
之前,我们会等待(Y g)
的第二个参数 - 并且取决于函数的作用(即,如果它具有基本情况),我们可能不需要完全评估(Y g)
。这就是Y
适用于名称评估的原因。
按值调用的修正方法是更改等式。而不是Y g = g (Y g)
,我们需要类似下面的等式:
Z g = g (λx. (Z g) x)
(我认为我的方程是正确的,或者接近正确。你可以计算出来,看看它是否符合Z
的定义。)
考虑到这一点的一种方法是,不是计算“整个递归函数”并将其交给g
,我们将它交给一个函数,它将一次一点地计算递归函数 - 和只有当我们真的需要更多它时才能将它应用于参数(x
)。