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

我的大脑似乎处于自虐模式,因此在被淹死之后, 这个和它 ,它想要在C#中使用一些DIY。

我想出了以下内容,我认为它不是Y-combinator,但似乎设法使递归递归的函数没有引用自身:

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

所以给出这些:

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

我们可以生成这些:

 Func Fact = Y(fact); Func 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))); 

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

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

你应该这样:

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

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

(编辑添加:) BTW,因为你使用C#模拟lambda演算用值调用评估,你想要的定点组合器通常称为Z,而不是Y

(再次编辑详细说明:)描述Y的等式是这样的(参见推导的维基百科页面 ):

 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一样,它在做任何事之前都在等待另一个论点。 所以我们在尝试进一步评估(Y g)之前等待g的第二个参数 – 并且取决于函数的作用(即,如果它具有基本情况),我们可能不需要评估(Y g)一点都不 这就是为什么Y适用于逐个名字的评估。

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

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

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

想到这一点的一种方法是不是计算“整个递归函数”并将其交给g ,我们将它交给一个函数,它将一次一点地计算递归函数 – 并且只有当我们实际需要一点时更多,所以我们可以将它应用于参数( x )。