什么是Y组合器?

时间:2020-03-06 14:21:52  来源:igfitidea点击:

Y组合器从事物的功能方面讲是一种计算机科学概念。大多数程序员对组合器一无所知,即使他们听说过它们也是如此。

  • 什么是Y组合器?
  • 组合器如何工作?
  • 它们有什么用?
  • 它们在程序语言中有用吗?

解决方案

如果我们准备长时间阅读,Mike Vanier会提供一个很好的解释。长话短说,它允许我们使用不一定本地支持的语言来实现递归。

定点组合器(或者定点运算符)是计算其他函数的定点的高阶函数。此操作与编程语言理论有关,因为它允许以重写规则的形式实现递归,而无需语言运行时引擎的明确支持。 (src维基百科)

JavaScript中的y-combinator:

var Y = function(f) {
  return (function(g) {
    return g(g);
  })(function(h) {
    return function() {
      return f(h(h)).apply(null, arguments);
    };
  });
};

var factorial = Y(function(recurse) {
  return function(x) {
    return x == 0 ? 1 : x * recurse(x-1);
  };
});

factorial(5)  // -> 120

编辑:
通过阅读代码,我学到了很多东西,但是如果没有一些背景方面的遗憾,很难理解这一点。利用其他答案提供的一些常识,我们可以开始区分正在发生的事情。

Y函数是" y组合器"。现在来看一下使用Y的" var factorial"行。请注意,我们将具有参数的函数(在此示例中为"递归")传递给该函数,该参数稍后还将在内部函数中使用。参数名称基本上成为内部函数的名称,允许它执行递归调用(因为它在定义中使用了recurse()。)y组合器执行了将否则为匿名内部函数与参数名称相关联的魔术传递给Y的函数。

有关Y如何做魔术的完整解释,请查看链接文章(不是我本人)。

Y组合器是"递归"(可以在其他函数上运行的函数),当我们不能从自身内部引用该函数时,它可以启用递归。在计算机科学理论中,它概括了递归,抽象了其实现,从而将其与所讨论功能的实际工作区分开。递归函数不需要编译时名称的好处是一种好处。 =)

这适用于支持lambda函数的语言。 lambda的基于表达式的性质通常意味着它们不能通过名称引用自己。通过声明该变量,对其进行引用,然后为该变量分配lambda来完成自引用循环,可以解决此问题。可以复制lambda变量,然后重新分配原始变量,这将破坏自引用。

Y组合器在静态类型的语言(通常是过程语言)中难以实现,并且经常使用,因为通常类型限制要求在编译时就知道该函数的参数数量。这意味着必须为需要使用的任何参数计数编写一个y-combinator。

以下是在C#中如何使用和使用Y-Combinator的示例。

使用Y组合器涉及构造递归函数的"异常"方式。首先,我们必须将函数编写为一段调用预先存在的函数的代码,而不是本身:

// Factorial, if func does the same thing as this bit of code...
x == 0 ? 1: x * func(x - 1);

然后,将其转换为需要调用一个函数的函数,并返回执行此操作的函数。之所以称为功能函数,是因为它接受一个功能,并对其执行一项操作,从而产生另一功能。

// A function that creates a factorial, but only if you pass in
// a function that does what the inner function is doing.
Func<Func<Double, Double>, Func<Double, Double>> fact =
  (recurs) =>
    (x) =>
      x == 0 ? 1 : x * recurs(x - 1);

现在,我们有一个函数,该函数需要一个函数,并返回另一个看起来像阶乘的函数,但它不会调用自身,而是调用传递到外部函数中的参数。我们如何使它成为阶乘?将内部函数传递给自身。 Y-Combinator通过使用具有永久名称的函数来做到这一点,它可以引入递归。

// One-argument Y-Combinator.
public static Func<T, TResult> Y<T, TResult>(Func<Func<T, TResult>, Func<T, TResult>> F)
{
  return
    t =>  // A function that...
      F(  // Calls the factorial creator, passing in...
        Y(F)  // The result of this same Y-combinator function call...
              // (Here is where the recursion is introduced.)
        )
      (t); // And passes the argument into the work function.
}

除了阶乘调用本身以外,发生的事情是阶乘调用阶乘生成器(由对Y-Combinator的递归调用返回)。并且,根据t的当前值,从生成器返回的函数将再次使用t 1调用生成器,或者仅返回1,终止递归。

它是复杂且神秘的,但在运行时都会彻底消失,其工作的关键是"延迟执行",以及拆分递归以覆盖两个功能。内部F作为参数传递,仅在必要时在下一次迭代中调用。

我已经在Clojure和Scheme中为Y-Combinator编写了一种"白痴指南",以帮助自己掌握它。他们受到"小策划者"中材料的影响

在方案中:
https://gist.github.com/z5h/238891

或者Clojure:
https://gist.github.com/z5h/5102747

这两个教程的代码都带有注释,并且应剪切并粘贴到我们喜欢的编辑器中。

这是Y-Combinator和Factorial函数的JavaScript实现(摘自Douglas Crockford的文章,网址为:http://javascript.crockford.com/little.html)。

function Y(le) {
    return (function (f) {
        return f(f);
    }(function (f) {
        return le(function (x) {
            return f(f)(x);
        });
    }));
}

var factorial = Y(function (fac) {
    return function (n) {
        return n <= 2 ? n : n * fac(n - 1);
    };
});

var number120 = factorial(5);