什么是Y-combinator?

时间:2008-09-18 15:21:03

标签: functional-programming computer-science theory definition combinators

Y-combinator是一种来自事物“功能”方面的计算机科学概念。大多数程序员对组合器一无所知,如果他们甚至听说过它们的话。

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

18 个答案:

答案 0 :(得分:278)

当你无法从内部引用函数时,Y-combinator是一个“函数”(对其他函数起作用的函数),它可以实现递归。在计算机科学理论中,它概括了递归,抽象了它的实现,从而将它与所讨论的函数的实际工作分开。不需要递归函数的编译时名称的好处是一种奖励。 =)

这适用于支持lambda functions的语言。 lambdas基于expression的性质通常意味着他们不能通过名字来引用自己。通过声明变量,引用它,然后将lambda分配给它来完成自引用循环来解决这个问题是很脆弱的。可以复制lambda变量,并重新分配原始变量,这会破坏自引用。

Y-combinators在static-typed种语言(procedural languages经常使用)中实现并经常使用是很麻烦的,因为通常的输入限制要求所讨论的函数的参数数量为在编译时已知。这意味着必须为需要使用的任何参数计数编写y组合子。

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

使用Y-combinator涉及构造递归函数的“不寻常”方式。首先,您必须将函数编写为调用预先存在的函数的代码,而不是自身:

// 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 作为参数传递,在下一次迭代中调用,仅在必要时

答案 1 :(得分:192)

如果您准备好长时间阅读,Mike Vanier has a great explanation。简而言之,它允许您使用一种本身不一定支持它的语言来实现递归。

答案 2 :(得分:98)

我从http://www.mail-archive.com/boston-pm@mail.pm.org/msg02716.html解除了这个问题,这是我几年前写的一个解释。

我将在此示例中使用JavaScript,但许多其他语言也可以使用。

我们的目标是能够编写1的递归函数 变量仅使用1个变量的函数而不是 作业,按名称定义事物等(为什么这是我们的 目标是另一个问题,让我们把它作为 我们给予的挑战。)似乎不可能,是吧?如 例如,让我们实现阶乘。

第1步是说我们可以轻松地做到这一点 骗了一点。使用2个变量的函数和 任务我们至少可以避免使用 用于设置递归的赋值。

// Here's the function that we want to recurse.
X = function (recurse, n) {
  if (0 == n)
    return 1;
  else
    return n * recurse(recurse, n - 1);
};

// This will get X to recurse.
Y = function (builder, n) {
  return builder(builder, n);
};

// Here it is in action.
Y(
  X,
  5
);

现在让我们看看我们是否可以减少欺骗。首先,我们正在使用 任务,但我们不需要。我们可以写X和 Y内联。

// No assignment this time.
function (builder, n) {
  return builder(builder, n);
}(
  function (recurse, n) {
    if (0 == n)
      return 1;
    else
      return n * recurse(recurse, n - 1);
  },
  5
);

但是我们使用2个变量的函数来获得1的函数 变量。我们可以修复吗?嗯,一个聪明的家伙的名字 如果你有更好的高阶,Haskell Curry有一个巧妙的技巧 函数然后你只需要1个变量的函数。该 证据是你可以从2(或更多)的函数中获得 一般情况下)变量为1变量与纯粹 像这样的机械文本转换:

// Original
F = function (i, j) {
  ...
};
F(i,j);

// Transformed
F = function (i) { return function (j) {
  ...
}};
F(i)(j);

其中......保持完全相同。 (这个技巧被称为 在其发明者之后“讨好”。 Haskell语言也是 以Haskell Curry命名。在无用的琐事下的文件。) 现在只需在任何地方应用此转换即可 我们的最终版本。

// The dreaded Y-combinator in action!
function (builder) { return function (n) {
  return builder(builder)(n);
}}(
  function (recurse) { return function (n) {
    if (0 == n)
      return 1;
    else
      return n * recurse(recurse)(n - 1);
  }})(
  5
);

随意尝试。 alert()返回,将它绑定到一个按钮,无论如何。 该代码以递归方式计算阶乘,而不使用 赋值,声明或2个变量的函数。 (但 试图追踪它是如何工作的可能会让你头晕目眩。 并且在没有推导的情况下处理它,只是稍微重新格式化 将导致代码肯定会困惑和混淆。)

您可以替换递归定义阶乘的4行 你想要的任何其他递归函数。

答案 3 :(得分:80)

我想知道是否有任何用途试图从头开始构建它。让我们来看看。这是一个基本的递归因子函数:

function factorial(n) {
    return n == 0 ? 1 : n * factorial(n - 1);
}

让我们重构并创建一个名为fact的新函数,该函数返回一个匿名析因计算函数,而不是自己执行计算:

function fact() {
    return function(n) {
        return n == 0 ? 1 : n * fact()(n - 1);
    };
}

var factorial = fact();

这有点奇怪,但它并没有错。我们只是在每一步产生一个新的阶乘函数。

此阶段的递归仍然相当明确。 fact函数需要知道自己的名字。让我们参数化递归调用:

function fact(recurse) {
    return function(n) {
        return n == 0 ? 1 : n * recurse(n - 1);
    };
}

function recurser(x) {
    return fact(recurser)(x);
}

var factorial = fact(recurser);

这很好,但recurser仍然需要知道自己的名字。让我们参数化:

function recurser(f) {
    return fact(function(x) {
        return f(f)(x);
    });
}

var factorial = recurser(recurser);

现在,不要直接调用recurser(recurser),而是创建一个返回其结果的包装函数:

function Y() {
    return (function(f) {
        return f(f);
    })(recurser);
}

var factorial = Y();

我们现在可以完全摆脱recurser名称;它只是Y的内部函数的一个参数,可以用函数本身代替:

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

var factorial = Y();

仍然引用的唯一外部名称是fact,但现在应该很清楚,这也很容易参数化,创建完整的通用解决方案:

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

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

答案 4 :(得分:48)

上面的大部分答案都描述了Y-combinator 的内容,而不是

Fixed point combinators用于表明lambda calculusturing complete。这是计算理论中非常重要的结果,为functional programming提供了理论基础。

学习定点组合器也帮助我真正理解函数式编程。我在实际编程中从未发现它们有任何用处。

答案 5 :(得分:23)

<{3}}中的

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-combinator”。现在看一下使用Y的var factorial行。请注意,您将一个函数传递给它,该函数具有一个参数(在此示例中为recurse),该参数稍后也会在内部函数中使用。参数名称基本上成为内部函数的名称,允许它执行递归调用(因为它在其定义中使用recurse()。)y-combinator执行将其他匿名内部函数与参数名称相关联的魔力传递给Y的函数。

有关Y如何完成魔法的完整解释,请查看JavaScript(不是我的顺便提一下)

答案 6 :(得分:17)

对于那些没有深入体验过函数式编程的程序员,现在不想开始,但是有点好奇:

Y组合子是一个公式,它允许您在函数不能具有名称但可以作为参数传递,用作返回值并在其他函数中定义的情况下实现递归。

它通过将函数作为参数传递给自身来工作,因此它可以调用自身。

它是lambda演算的一部分,它实际上是数学,但实际上是一门编程语言,对于计算机科学尤其是函数式编程来说非常重要。

Y组合器的日常实用价值有限,因为编程语言倾向于让您命名功能。

如果您需要在警察阵容中识别它,它看起来像这样:

  

Y =λf。(λx.f(x x))(λx.f(x x))

由于重复(λx.f (x x)),您通常可以发现它。

λ符号是希腊字母lambda,它为lambda演算提供了名称,并且有很多(λx.t)样式术语,因为这就是lambda演算的样子。

答案 7 :(得分:12)

其他答案提供了非常简洁的答案,没有一个重要的事实:你不需要用这种复杂的方式用任何实用语言实现定点组合器,这样做没有实际意义(除了“看,我知道什么Y-combinator是“)。这是重要的理论概念,但没有什么实际价值。

答案 8 :(得分:6)

这是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);

答案 9 :(得分:6)

Y-Combinator是助焊剂电容器的另一个名称。

答案 10 :(得分:5)

我在Clojure和Scheme中为Y-Combinator写了一篇“白痴指南”,以帮助自己掌握它。他们受到“The Little Schemer”中材料的影响

在计划中: https://gist.github.com/z5h/238891

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

两个教程都是散布着评论的代码,应该剪切和放大。可以进入你最喜欢的编辑。

答案 11 :(得分:4)

y-combinator实现匿名递归。而不是

function fib( n ){ if( n<=1 ) return n; else return fib(n-1)+fib(n-2) }

你可以做到

function ( fib, n ){ if( n<=1 ) return n; else return fib(n-1)+fib(n-2) }

当然,y-combinator仅适用于按名称呼叫的语言。如果你想在任何普通的按值调用语言中使用它,那么你将需要相关的z-combinator(y-combinator将发散/无限循环)。

答案 12 :(得分:4)

以下是original questions的答案,the article汇总了answer by Nicholas Mancuso中提到的(TOTALY值得一读),以及其他答案:

  

什么是Y-combinator?

Y-combinator是一个&#34;功能性&#34; (或高阶函数 - 对其他函数起作用的函数),它接受一个参数,这是一个不递归的函数,并返回递归函数的一个版本。

有点递归=),但更深入的定义:

组合子 - 只是一个没有自由变量的lambda表达式。
自由变量 - 是一个不是绑定变量的变量。
Bound变量 - 包含在lambda表达式主体内部的变量,该变量名称作为其参数之一。

考虑这个问题的另一种方法是组合器就是这样一个lambda表达式,在这个表达式中,你可以用它的定义替换组合名称,无论它在哪里找到并且一切都仍然有用(你将进入无限循环)如果组合器在lambda体内包含对自身的引用。)

Y-combinator是一个定点组合器。

函数的固定点是函数域的一个元素,由函数映射到自身。
也就是说,如果cf(x)是函数f(c) = c的固定点
这意味着f(f(...f(c)...)) = fn(c) = c

  

组合子如何工作?

以下示例假设强+动态输入:

懒惰(正常顺序)Y-combinator:
此定义适用于具有延迟(也称为延迟,按需调用)评估的语言 - 评估策略将表达式的评估延迟到需要它的值。

Y = λf.(λx.f(x x)) (λx.f(x x)) = λf.(λx.(x x)) (λx.f(x x))

这意味着,对于给定函数f(这是一个非递归函数),可以首先通过计算λx.f(x x)获取相应的递归函数,然后应用此lambda表达式对自己。

严格(应用订单)Y-combinator:
此定义适用于具有严格(也是:急切,贪婪)评估的语言 - 评估策略,其中表达式一旦被绑定到变量就会被评估。

Y = λf.(λx.f(λy.((x x) y))) (λx.f(λy.((x x) y))) = λf.(λx.(x x)) (λx.f(λy.((x x) y)))

它的性质与懒惰相同,它只有一个额外的λ包装器来延迟lambda的身体评估。我问another question,与此主题有些相关。

  

它们有什么用处?

answer by Chris Ammerman 借来的:Y-combinator推广递归,抽象其实现,从而将其与相关函数的实际工作分开。

即使Y-combinator有一些实际的应用,它主要是一个理论概念,理解它将扩展你的整体愿景,并且可能会提高你的分析和开发技能。

  

它们在程序语言中有用吗?

作为stated by Mike Vanier可以在许多静态类型语言中定义Y组合子,但是(至少在我看过的例子中)这样的定义通常需要一些非显而易见的类型hackery,因为Y组合器本身并不具有直接的静态类型。这超出了本文的范围,因此我不会进一步提及

并且mentioned by Chris Ammerman:大多数过程语言都有静态类型。

所以回答这个 - 不是真的。

答案 13 :(得分:4)

匿名递归

定点组合子是一个高阶函数fix,根据定义,它满足等价

forall f.  fix f  =  f (fix f)

fix f表示定点方程

的解x
               x  =  f x

自然数的阶乘可以通过

来证明
fact 0 = 1
fact n = n * fact (n - 1)

使用fix,可以推导出一般/μ递归函数的任意建设性证明,而不需要非自然的指称性。

fact n = (fix fact') n

,其中

fact' rec n = if n == 0
                then 1
                else n * rec (n - 1)

这样

   fact 3
=  (fix fact') 3
=  fact' (fix fact') 3
=  if 3 == 0 then 1 else 3 * (fix fact') (3 - 1)
=  3 * (fix fact') 2
=  3 * fact' (fix fact') 2
=  3 * if 2 == 0 then 1 else 2 * (fix fact') (2 - 1)
=  3 * 2 * (fix fact') 1
=  3 * 2 * fact' (fix fact') 1
=  3 * 2 * if 1 == 0 then 1 else 1 * (fix fact') (1 - 1)
=  3 * 2 * 1 * (fix fact') 0
=  3 * 2 * 1 * fact' (fix fact') 0
=  3 * 2 * 1 * if 0 == 0 then 1 else 0 * (fix fact') (0 - 1)
=  3 * 2 * 1 * 1
=  6

这个正式的证明

fact 3  =  6

有条理地使用重写的定点组合器等价

fix fact'  ->  fact' (fix fact')

Lambda演算

无类型lambda演算形式主义包含无上下文语法

E ::= v        Variable
   |  λ v. E   Abstraction
   |  E E      Application

其中v范围超出变量,以及 beta eta reduction 规则

(λ x. B) E  ->  B[x := E]                                 Beta
  λ x. E x  ->  E          if x doesn’t occur free in E   Eta

Beta缩减用表达式(“argument”)x替换抽象(“function”)body B中变量E的所有自由出现次数。 Eta减少消除了冗余抽象。形式主义有时会省略它。不适用缩减规则的 irreducible 表达式采用正常规范形式

λ x y. E

的简写
λ x. λ y. E

(抽象多元化),

E F G

的简写
(E F) G

(申请左关联),

λ x. x

λ y. y

alpha-equivalent

抽象和应用程序是lambda演算的两个唯一“语言基元”,但它们允许编码任意复杂的数据和操作。

教会数字是与Peano-axiomatic naturals类似的自然数字的编码。

   0  =  λ f x. x                 No application
   1  =  λ f x. f x               One application
   2  =  λ f x. f (f x)           Twofold
   3  =  λ f x. f (f (f x))       Threefold
    . . .

SUCC  =  λ n f x. f (n f x)       Successor
 ADD  =  λ n m f x. n f (m f x)   Addition
MULT  =  λ n m f x. n (m f) x     Multiplication
    . . .

的正式证明
1 + 2  =  3

使用β缩减的重写规则:

   ADD                      1            2
=  (λ n m f x. n f (m f x)) (λ g y. g y) (λ h z. h (h z))
=  (λ m f x. (λ g y. g y) f (m f x)) (λ h z. h (h z))
=  (λ m f x. (λ y. f y) (m f x)) (λ h z. h (h z))
=  (λ m f x. f (m f x)) (λ h z. h (h z))
=  λ f x. f ((λ h z. h (h z)) f x)
=  λ f x. f ((λ z. f (f z)) x)
=  λ f x. f (f (f x))                                       Normal form
=  3

组合子

在lambda演算中,组合器是不包含自由变量的抽象。最简单的是:I,身份组合者

λ x. x

与身份功能同构

id x = x

这样的组合子是组合子结石的原始算子,就像SKI系统一样。

S  =  λ x y z. x z (y z)
K  =  λ x y. x
I  =  λ x. x

Beta缩减不是强烈正常化;并非所有可简化的表达式,“重新索引”,在β减少下收敛到正常形式。一个简单的例子是欧米茄ω组合子的不同应用

λ x. x x

自己:

   (λ x. x x) (λ y. y y)
=  (λ y. y y) (λ y. y y)
. . .
=  _|_                     Bottom

优先考虑最左侧子表达式(“头部”)的减少。 适用订单在替换前规范化参数,正常订单不规范化。这两种策略类似于急切的评估,例如: C和懒惰评估,例如Haskell中。

   K          (I a)        (ω ω)
=  (λ k l. k) ((λ i. i) a) ((λ x. x x) (λ y. y y))

在急切的应用顺序β减少下出现分歧

=  (λ k l. k) a ((λ x. x x) (λ y. y y))
=  (λ l. a) ((λ x. x x) (λ y. y y))
=  (λ l. a) ((λ y. y y) (λ y. y y))
. . .
=  _|_

因为严格语义

forall f.  f _|_  =  _|_

但收敛于懒惰的正常β降低

=  (λ l. ((λ i. i) a)) ((λ x. x x) (λ y. y y))
=  (λ l. a) ((λ x. x x) (λ y. y y))
=  a

如果表达式具有正常形式,则正常顺序β减少将找到它。

ý

Y 定点组合器的基本属性

λ f. (λ x. f (x x)) (λ x. f (x x))

提供
   Y g
=  (λ f. (λ x. f (x x)) (λ x. f (x x))) g
=  (λ x. g (x x)) (λ x. g (x x))           =  Y g
=  g ((λ x. g (x x)) (λ x. g (x x)))       =  g (Y g)
=  g (g ((λ x. g (x x)) (λ x. g (x x))))   =  g (g (Y g))
. . .                                      . . .

等价

Y g  =  g (Y g)

同构
fix f  =  f (fix f)

无类型lambda演算可以编码一般/μ递归函数的任意构造证明。

 FACT  =  λ n. Y FACT' n
FACT'  =  λ rec n. if n == 0 then 1 else n * rec (n - 1)

   FACT 3
=  (λ n. Y FACT' n) 3
=  Y FACT' 3
=  FACT' (Y FACT') 3
=  if 3 == 0 then 1 else 3 * (Y FACT') (3 - 1)
=  3 * (Y FACT') (3 - 1)
=  3 * FACT' (Y FACT') 2
=  3 * if 2 == 0 then 1 else 2 * (Y FACT') (2 - 1)
=  3 * 2 * (Y FACT') 1
=  3 * 2 * FACT' (Y FACT') 1
=  3 * 2 * if 1 == 0 then 1 else 1 * (Y FACT') (1 - 1)
=  3 * 2 * 1 * (Y FACT') 0
=  3 * 2 * 1 * FACT' (Y FACT') 0
=  3 * 2 * 1 * if 0 == 0 then 1 else 0 * (Y FACT') (0 - 1)
=  3 * 2 * 1 * 1
=  6

(乘法延迟,汇合)

对于Churchian无类型lambda演算,除了Y之外,已经证明存在递归可枚举的无限定点组合。

 X  =  λ f. (λ x. x x) (λ x. f (x x))
Y'  =  (λ x y. x y x) (λ y x. y (x y x))
 Z  =  λ f. (λ x. f (λ v. x x v)) (λ x. f (λ v. x x v))
 Θ  =  (λ x y. y (x x y)) (λ x y. y (x x y))
  . . .

正常的β阶减少使得未扩展的无类型lambda演算成为图灵完备的重写系统。

在Haskell中,定点组合器可以优雅地实现

fix :: forall t. (t -> t) -> t
fix f = f (fix f)

在评估所有子表达式之前,Haskell的懒惰正常化为有限性。

primes :: Integral t => [t]
primes = sieve [2 ..]
   where
      sieve = fix (\ rec (p : ns) ->
                     p : rec [n | n <- ns
                                , n `rem` p /= 0])

答案 14 :(得分:4)

作为组合者的新手,我发现Mike Vanier's article(感谢Nicholas Mancuso)真的很有帮助。我想写一个摘要,除了记录我的理解之外,如果它对其他人有帮助我会很高兴。

Crappy Less Crappy

使用factorial作为示例,我们使用以下almost-factorial函数计算数字因子x

def almost-factorial f x = if iszero x
                           then 1
                           else * x (f (- x 1))

在上面的伪代码中,almost-factorial接受函数f并且数字xalmost-factorial是curry,因此可以将其视为函数{{ 1}}并返回1-arity函数。)

f计算almost-factorial的阶乘时,它会将x的阶乘计算委托给函数x - 1,并将结果与​​f一起累积(在此case,它将(x - 1)的结果乘以x)。

可以看出x接受了糟糕的版本的阶乘函数(只能计算到数字almost-factorial)并返回 less-crappy 版本的factorial(计算到数字x - 1)。就像这种形式一样:

x

如果我们反复将 less-crappy 版本的factorial传递给almost-factorial crappy-f = less-crappy-f ,我们最终会获得所需的阶乘函数almost-factorial。它可以被视为:

f

固定点

almost-factorial f = f 表示almost-factorial f = f的事实是函数f修复点

这是一种非常有趣的方式来看到上述功能的关系,这对我来说是一个很好的时刻。 (如果你没有,请阅读Mike关于修复点的帖子)

三个功能

为了概括,我们有一个非递归函数almost-factorial(就像我们的几乎因子一样),我们有 fix-point 函数{{1 (与我们的f一样),然后fn在您fr Y时提供的内容,Y会返回fn的定点函数。< / p>

总结(假设Y仅采用一个参数进行简化; fn退化为frx ...递归):

  • 我们将核心计算定义为 x - 1 x - 2,这是 几乎有用的 功能 - 虽然我们无法直接在fn上使用def fn fr x = ...accumulate x with result from (fr (- x 1)),但很快就会有用。这种非递归fn使用函数x来计算其结果
  • fn fr fn fr = fr的固定点,fr 有用的 功能,我们可以fn使用fr来获取结果
  • fr x 会返回函数的定点,Y fn = fr 会使我们的几乎有用将函数Y添加到有用的 Y

派生fn(不包括在内)

我将跳过fr的推导并继续理解Y。 Mike Vainer的帖子有很多细节。

Y

的形式

Y定义为( lambda calculus 格式):

Y

如果我们替换函数左侧的变量Y,我们得到

Y f = λs.(f (s s)) λs.(f (s s))

确实,s的结果是Y f = λs.(f (s s)) λs.(f (s s)) => f (λs.(f (s s)) λs.(f (s s))) => f (Y f) 的定点。

为什么(Y f)有效?

根据f的签名,(Y f)可以是任何arity的函数,为简化起见,我们假设f只接受一个参数,例如我们的阶乘函数。

(Y f)

(Y f)以来,我们继续

def fn fr x = accumulate x (fr (- x 1))

当最内层fn fr = fr为基本案例且=> accumulate x (fn fr (- x 1)) => accumulate x (accumulate (- x 1) (fr (- x 2))) => accumulate x (accumulate (- x 1) (accumulate (- x 2) ... (fn fr 1))) 在计算中未使用(fn fr 1)时,递归计算终止。

再次关注fn

fr

所以

Y

对我来说,这个设置的神奇部分是:

  • fr = Y fn = λs.(fn (s s)) λs.(fn (s s)) => fn (λs.(fn (s s)) λs.(fn (s s))) fr x = Y fn x = fn (λs.(fn (s s)) λs.(fn (s s))) x 相互依赖:fn'包裹'fr内部,每次fr用于计算fn ,它'生成'('升降机'?)和fr并将计算委托给x(自己传递fnfn);另一方面,fr取决于x并使用fn来计算较小问题fr的结果。
  • fr用于定义x-1fr在其操作中使用fn时,尚未定义真实fn
  • 定义真实业务逻辑的fr。基于frfn创建fn - 特定形式的辅助函数 - 以便于以递归方式计算Y

这帮助我此刻理解fr,希望它有所帮助。

顺便说一句,我也发现这本书An Introduction to Functional Programming Through Lambda Calculus非常好,我只是参与其中而且我无法理解书中的fn这一事实让我想到这篇文章

答案 15 :(得分:3)

这个操作员可以简化你的生活:

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

然后你避免了额外的功能:

var fac = Y(function(n) {
    return n == 0 ? 1 : n * this(n - 1);
});

最后,请致电fac(5)

答案 16 :(得分:3)

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

答案 17 :(得分:0)

我认为回答此问题的最佳方法是选择一种语言,例如JavaScript:

function factorial(num)
{
    // If the number is less than 0, reject it.
    if (num < 0) {
        return -1;
    }
    // If the number is 0, its factorial is 1.
    else if (num == 0) {
        return 1;
    }
    // Otherwise, call this recursive procedure again.
    else {
        return (num * factorial(num - 1));
    }
}

现在重写它,以便它不使用函数内部函数的名称,但仍然以递归方式调用它。

应该看到函数名称factorial的唯一位置是在呼叫站点。

提示:您不能使用函数名称,但可以使用参数名称。

解决问题。不要抬头看。一旦你解决了它,你就会明白y-combinator解决了什么问题。