Y 组合子详解 (The Y Combinator)

如何实现一个匿名递归函数?

Is there any way to make an anonymous function call itself?

不动点

f(g)=g

这个 g 就是 f 的不动点(Fixed Point)。

A fixed point of a function g , is a value that is mapped to itself by the function f.

什么是 Y 组合子

Y 组合子(The Y combinator) , discovered by Haskell B. Curry, is defined as:

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

用于计算(高阶)函数的不动点,使得lambda演算可以定义匿名递归函数。

Let’s see that in action:

X = (λx. f (x x))(λx. f (x x))
X = f (x x) [x := λx. f (x x)]
X = f ((λx. f (x x)) (λx. f (x x)))
X = f X

In functional programming, the Y combinator can be used to formally define recursive functions in a programming language that does not support recursion.

It turns out that for any λ-expression f,

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

is a fixed-point of f.

lambda 演算

所谓lambda演算是一种计算模型,在这种计算模型中,一切都是函数,连数值都可以没有(用函数表示)。它具有和图灵机等价的计算能力,但是和图灵机偏硬件的描述方式不同,lambda演算(基本上)只依赖两条数学推理规则。

虽然lambda演算中一切都是函数,但都可以不需要函数名。没有函数名的函数称为「匿名函数」。

比如平方函数 sqr (x) = x * x ,在lambda演算中写成:

lambda x . x * x

表明对参数 x 执行自己乘以自己的操作。任何用到 sqr (x) 的地方,直接用这个lambda式子替换就可以。

α-equivalent:identity function

λx. x is known as the identity function

That is, it takes in some input x, and outputs the same x.

You might ask, “Isn’t λy. y also the identity function?” Indeed it is! λx. x and λy. y are α-equivalent (alpha-equivalent). In fact, all of the following are α-equivalent:

λx. x
λy. y
λz. z
λ☃. ☃

constant function

And what about

λx. y

constant function! it ignores the input x and returns y no matter what.

Bound vs. free variables

For example:

x is a bound variable in λx.x

but x is a free variable in (λy. y) x.

β-reduction:function application

(λx. x) y

上面的表达式:以 y 为入参调用函数 λx. x , 得到的结果就是 y。

The rules of β-reduction say that a term

 (λx. t) s

can be reduced to

t [x := s]

Line by line, it looks like this:

(λx. x) s
x [x := s]
s

For example, take a look at the following term:

λy.(λx. x) y

… and feed it two inputs, a and b:

(λy.(λx. x) y) a b
((λx. x) y) [y := a]) b
(λx. x) a b
(x [x := a]) b
a b

Now take a look at the following λ-term:

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

What happens when you apply β-reduction to it?

(λx. x x)(λx. x x)
(x x) [x := (λx. x x)]
(λx. x x)(λx. x x)

从计算阶乘说起

数学中的阶乘计算如下:

  factorial 0 = 1factorial 1 = 1 * 1factorial 2 = 2 * 1 = 2factorial 3 = 3 * 2 * 1 = 6factorial 4 = 4 * 3 * 2 * 1 = 24

公式化的定义就是:

factorial(0) = 1
factorial(n) = n*factorial(n-1) (n>0)

用 Lisp 实现代码如下:

(defun factorial (n)(if (= 0 n) 1(* n (factorial (- n 1)))))(factorial 4)
24

这个函数在内部递归调用了自身,调用自身需要函数本体的名字,这个函数叫 factorial 。

从上面的例子可以看出,递归函数的一个关键点就在于调用自身,而调用自身必须要知自身的函数名,这样才能通过作用域链访问到函数对自己的声明和定义。

BUT, λ-calculus does not allow this kind of self-reference, at least not directly.

匿名递归函数

函数在内部递归调用了自身,这就叫递归。

至今为止,我们提到的函数,都是有名字的。

回到一开始提出的问题:如何实现一个匿名递归函数?

但是当需要定义的函数含有递归时,比如阶乘 factorial,我们习惯的方式是 (Kotlin 语言):

package com.light.sword.ycombinatorfun factorial(n: Int): Int {return when (n) {0 -> 1else -> n * factorial(n - 1)}
}fun main() {val a = factorial(4)println(a) // 24
}

上面的阶乘函数 factorial 直接写成lambda演算则是

factorial (x) = lambda x. (x == 0) ? 1 : x * factorial (x - 1)

这时候函数的定义部分引用了函数自身,但是,没有函数名意味着用无法直接引用函数自身。怎么办呢?

The Y combinator:终结

她又来了:

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

希望你现在看她觉得熟悉了。

现在让我们回到阶乘函数 factorial . 如果函数可以引用自身,那么递归就很简单:

f := λx.(if x == 0 then 1 else x * f (x–1))

但是,我们知道 λ 演算中不许这么做。

那我们换个角度思考一下吧。

我们来定义一个高阶函数 F , 入参是函数 f :

F := λf. λx.(if x == 0 then 1 else x * f (x–1))

然后,我们想求解 F 的不动点:

F(f) = f

这样我们就可以使用 F(f) 进行“间接自引用” 来实现递归。

我们已经知道,对任意 λ-expression f

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

is a fixed-point of f.

Let’s see that in action again:

X = (λx.f (x x))(λx.f (x x))
X = f (x x) [x := λx. f (x x)]
X = f ((λx. f (x x)) (λx. f *(x x)))
X = f X

As you can see, for any arbitrary function f, the input X remains unchanged.

Given this, we can build a function that returns a fixed-point for any function f by taking the function in as an argument:

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

And that right there is the Y combinator.

各大编程语言实现 Y combinator 函数

编程语言的另一个重要分界线是静态类型动态类型

静态类型语言是在编译时确定所有表达式类型的语言,任何类型错误都会导致编译失败。

动态类型语言直到运行时才进行任何类型检查,并且如果将函数应用于无效类型的参数(*例如,*通过尝试将整数和字符串相加,然后报告错误。

在常用的编程语言中,C,C ++和Java是静态类型的,Perl,Python和Ruby是动态类型的。Scheme(我们将用于示例的语言)也是动态类型的。(也有跨越静态类型和动态类型之间边界的语言)

人们经常听到静态类型,称为强类型和动态类型,称为弱类型,但这是滥用术语。强类型只是意味着语言中的每个值都只有一种类型,而弱类型意味着某些值可以有多种类型。因此,动态类型的Scheme也是强类型的,而静态类型的C是弱类型的(因为您可以将指向一种对象的指针转换为指向另一种类型的对象而不改变指针的值) 。

事实证明,在动态类型语言中定义Y组合器要简单得多。可以用许多静态类型语言定义Y组合子,但是这样的定义通常需要一些hackery,因为Y组合子本身没有简单的静态类型。

Common Lisp

实现 Y 组合子:

(defun Y (f)((lambda (g) (funcall g g))(lambda (g)(funcall f (lambda (&rest a)(apply (funcall g g) a))))))

用 Y 组合子实现阶乘与斐波那契数列:

(defun fac (n)(funcall(Y (lambda (f)(lambda (n)(if (zerop n)1(* n (funcall f (1- n)))))))n))(defun fib (n)(funcall(Y (lambda (f)(lambda (n a b)(if (< n 1)a(funcall f (1- n) b (+ a b))))))n 0 1))(mapcar #'fac '(1 2 3 4 5 6 7 8 9 10))
(1 2 6 24 120 720 5040 40320 362880 3628800))(mapcar #'fib '(1 2 3 4 5 6 7 8 9 10))
(1 1 2 3 5 8 13 21 34 55)

C

C doesn’t have first class functions, so we demote everything to second class to match.

#include <stdio.h>
#include <stdlib.h>/* func: our one and only data type; it holds either a pointer toa function call, or an integer.  Also carry a func pointer toa potential parameter, to simulate closure                   */
typedef struct func_t func;
typedef struct func_t {func (fn) (func, func);func _;int num;
} func_t;func new(func(f)(func, func), func ) {func x = malloc(sizeof(func_t));x->fn = f;x-> = _;       /* closure, sort of */x->num = 0;return x;
}func call(func f, func n) {return f->fn(f, n);
}func Y(func(*f)(func, func)) {func g = new(f, 0);g->_ = g;return g;
}func num(int n) {func x = new(0, 0);x->num = n;return x;
}func fac(func self, func n) {int nn = n->num;return nn > 1   ? num(nn * call(self->_, num(nn - 1))->num): num(1);
}func fib(func self, func n) {int nn = n->num;return nn > 1? num(  call(self->, num(nn - 1))->num +call(self->, num(nn - 2))->num ): num(1);
}void show(func n) { printf(" %d", n->num); }int main() {int i;func f = Y(fac);printf("fac: ");for (i = 1; i < 10; i++)show( call(f, num(i)) );printf("\n");f = Y(fib);printf("fib: ");for (i = 1; i < 10; i++)show( call(f, num(i)) );printf("\n");return 0;}

Output:

fac: 1 2 6 24 120 720 5040 40320 362880
fib: 1 2 3 5 8 13 21 34 55

JavaScript

var Y = f => (x => x(x))(y => f(x => y(y)(x)));
var fac = Y(f => n => n > 1 ? n * f(n-1) : 1);fac(10)
3628800

Kotlin

package com.light.sword.ycombinator/*** FP: Y Combinator** lambda f. (lambda x. (f(x x)) lambda x. (f(x x)))* Created by jack on 2017/7/9.*/typealias G<T, R> = (T) -> Rinterface F<T, R> : Function1<F<T, R>, G<T, R>>fun <T, R> f(block: (F<T, R>) -> G<T, R>) = object : F<T, R> {// 调用函数自身override fun invoke(g: F<T, R>) = block(g)
}typealias E<T, R> = Function1<G<T, R>, G<T, R>>/*** Y 组合子函数* @param e :E 入参,是一个函数类型 Function1<G<T, R>, G<T, R>>*/
fun <T, R> Y(e: E<T, R>) = ({ g: F<T, R> -> g(g) })(f { g ->e { x -> g(g)(x) }
})/*** 用 Y 组合子实现 factorial 阶乘函数*/
val factorial: (Int) -> Int = Y { f ->{ x ->if (x == 0) 1 else x * f(x - 1)}
}/*** 用 Y 组合子实现 fibonacci 数列函数 fib: (T)->R* Y(fib) = fib* 可以推断: Y(Y(fib)) = Y(fib) = fib*/
val fib: (Int) -> Int = Y { f ->{ x ->if (x == 1 || x == 2) 1 else f(x - 1) + f(x - 2)}
}fun main() {println(fib(10)) // 55println(factorial(10)) // 3628800
}

参考资料

The Y Combinator (no, not that one) A crash-course on lambda calculus:

https://medium.com/@ayanonagon/the-y-combinator-no-not-that-one-7268d8d9c46

The Y Combinator (Slight Return):

https://mvanier.livejournal.com/2897.html

Y combinator:

https://rosettacode.org/wiki/Y_combinator#Kotlin


Y 组合子详解 (The Y Combinator)相关推荐

  1. 【逻辑与计算理论】组合子逻辑与 Y 组合子

    为什么是Y? 在前面的几个帖子里,我已经建立了如何把lambda演算变成一个有用的系统的点点滴滴. 我们已经有了数字,布尔值和选择运算符.我们唯一欠缺的是重复. 这个有点棘手.lambda演算使用递归 ...

  2. 关于python中Y组合子的问题讨论

    关于python中Y组合子的问题讨论 by Wenze Jin 在 The Structure and Interpretation of Computer Programs 这门课的hw03-Rec ...

  3. 设计模式之组合模式详解(附应用举例实现)

    文章目录 1 组合模式介绍 2 组合模式详解 2.1 组合模式结构 2.2 组合模式实现 2.3 组合模式应用举例 3 透明组合模式和安全组合模式 1 组合模式介绍 在我们的树形目录结构中,包含文件和 ...

  4. 组合模式详解附有代码案例分析(包含透明组合模式、安全组合模式的代码示例)

    组合模式 一.组合模式的概念和角色 (一).组合模式的概念 (二).组合模式的角色 二.组合模式的应用场景 三.透明组合模式的代码示例 四.安全组合模式的代码示例 五.组合模式的优缺点 (一).优点 ...

  5. python中x y表示_Python中表达式x += y和x = x+y 的区别详解

    前言 本文主要给大家介绍的是关于Python中表达式x += y和x = x+y 区别的相关内容,分享出来供大家参考学习,下面来看看详细的介绍: 直接看下面代码: x +=y In [66]: id( ...

  6. Java设计模式之组合模式详解

    文章目录 详解Java设计模式之组合模式 案例引入 组合模式 定义 模式类图结构 相关角色 典型代码 案例分析 类图设计 实例代码 结果分析 JavaJDK中的组合模式 透明组合模式 安全组合模式 组 ...

  7. mysql sql组合_详解mysql 组合查询

    使用UNION 多数SQL查询都只包含一个或多个表中返回数据的单条SELECT语句.MySQL也允许执行多个查询(多条SELECT语句),并将结果作为单个查询结果集返回.这些组合查询通常称为并(uni ...

  8. 【原创】组合逻辑电路详解、实现及其应用

    作者:造就狂野青春 本文为明德扬原创及录用文章,转载请注明出处! 一. 什么是组合逻辑电路? 在数字电路中,根据逻辑功能的不同,我们可以将数字电路分成两大类,一类叫做组合逻辑电路.另一类叫做时序逻辑电 ...

  9. 郭家宝2012年写的PPT,讲解Y组合子,讲得很好

    原文:byvoid.github.io/slides/apio- 转载于:https://juejin.im/post/5c7e7927518825408b658afe

  10. java设计模式组合模式详解_《JAVA设计模式》之组合模式(Composite)

    在阎宏博士的<JAVA与模式>一书中开头是这样描述合成(Composite)模式的:html 合成模式属于对象的结构模式,有时又叫作"部分--总体"模式.合成模式将对象 ...

最新文章

  1. Python面试必知(四)
  2. Qt Creator添加Qt Designer插件
  3. socket和http协议
  4. Sublime Text for Windows的快捷键
  5. centos7 安装git_Centos7.4 Yapi 服务搭建
  6. Grafana中文版本 1
  7. 利用自己的祖源成分结果画祖源成分饼图
  8. netlogo笔记(4):森林火灾模拟与模型改进
  9. 实验二 贪吃蛇游戏开发
  10. OCLint + Infer + Jenkins + SonarQube 搭建iOS代码静态分析系统
  11. ce修改面板属性_改属性【dnfce修改器吧】_百度贴吧
  12. 【深度学习】注意力机制
  13. CSS基础五(盒模型)
  14. 【汇正财经】大盘低开回升
  15. 一个简单的ASP登录页面
  16. java如何设置网页全屏_java中如何进行全屏方式和窗口方式的转换 详细??
  17. 浙大PTA-Python题库 编程题第五章(5-1~5-11)题解
  18. bat启动putty连接串口
  19. 在CODESYS中通过EtherCAT总线驱动单个电机
  20. BoCloud博云作为核心编写单位参与首个微服务标准制定

热门文章

  1. vivado2019.2安装+license添加教程
  2. KETTLE-JDBC连接池参数介绍以及使用
  3. 各级期刊分类以及含金量//2021-2-4
  4. 黑马程序员-java学习笔记_整理黑马官网Java自学27天基础视频及笔记
  5. Linux安装yum过程(超详细!)
  6. secoclient-linux-64 安装与卸载
  7. idea 集成svn
  8. Xshell 6免费版
  9. 如何在线伪造邮箱发件人,用任意邮箱发送邮件
  10. bp神经网络预测模型流程图,bp神经网络实例分析