函数式编程的数学基础(四)邱奇数的除法,递归

邱奇数的除法实现较为困难。

但有了邱奇数的减法和比较,那么我们大致可以想到,如果能够不断把被除数减掉除数,直到它变得小于除数,我们就可以实现邱奇数除法了。

然而问题在于,我们如何在Lambda演算中实现这种"循环"?

最容易想到的是用递归代替循环,那么,Lambda函数能否直接递归呢?

答案是不行,我们使用一个Lambda函数前必须要先定义它。

Y Combinator

于是我们尝试构造一个可以递归的环境,假设有一个生成器函数 <math xmlns="http://www.w3.org/1998/Math/MathML"> Y Y </math>Y,它能够提供给一个函数f以包含自身的上下文,即:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> f = Y λ f . ( . . . . . . ) f = Y\ \lambda f.(......) </math>f=Y λf.(......)

这样我们就可以在省略号处正常编写递归函数了。于是问题的关键转化为为,如何编写实现这个函数 <math xmlns="http://www.w3.org/1998/Math/MathML"> Y Y </math>Y,我们首先写一个Y的形式,此处g应该是我们前面所写用于生成f的函数。
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> Y = λ g . ? Y = \lambda g. ? </math>Y=λg.?

我们希望给g传入f,即:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> Y = λ g . ( g f ) Y = \lambda g.(g\ f) </math>Y=λg.(g f)

但我们又没有f,为了生成f,我们又要调用g,于是要写成:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> Y = λ g . ( g ( g ( g . . . . . . ) ) ) Y = \lambda g.(g\ (g\ (g\ ......))) </math>Y=λg.(g (g (g ......)))

看起来这样无穷调用,写不出来。

我们来换个思路,我们没有必要构造一个真实的f,我们只需要给传入一个跟f完全一致的函数就可以了。我们可以写一个虚假的F
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> F = λ x . ( g f x ) F = \lambda x.(g\ f\ x) </math>F=λx.(g f x)

但这里面仍然用到f,因为F完全等价于f,所以我们可以写:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> F = λ x . ( g F x ) F = \lambda x.(g\ F\ x) </math>F=λx.(g F x)

这个写法仍然递归,但是因为F是我们自己定义的,所以我们可以构造一个把函数传给自己的结构
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> λ F . ( F F ) \lambda F.(F\ F) </math>λF.(F F)

再把F改写做:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> F = λ F . ( g ( F F ) ) F = \lambda F.(g\ (F\ F)) </math>F=λF.(g (F F))

于是最终写法是:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> Y = λ g . ( λ F . ( F F ) ) ( λ F . λ x . ( g ( F F ) x ) ) Y = \lambda g.(\lambda F.(F\ F))\ (\lambda F.\lambda x.(g\ (F\ F)\ x)) </math>Y=λg.(λF.(F F)) (λF.λx.(g (F F) x))

注:如果不考虑实际编程产生死循环,Y组合子可以写作更简单的形式:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> Y = λ g . ( λ F . ( F F ) ) ( λ F . ( g ( F F ) ) ) Y = \lambda g.(\lambda F.(F\ F))\ (\lambda F.(g\ (F\ F))) </math>Y=λg.(λF.(F F)) (λF.(g (F F)))

这样我们就有了函数递归的能力,我们不妨把公式翻译成JavaScript代码验证下我们的推导:

JavaScript 复制代码
const Y = g => (F => F(F))(F => x => g(F(F))(x));
const sum = Y(sum => n => n > 0 ? n + sum(n - 1) : 0);
sum(100); //5050

由此我们可以看到,无论语言是否支持递归,只要函数具有闭包性质和一等公民身份,我们都可以基于lambda理论实现递归。

这里的特殊函数Y,我们把它称作Y Combinator ,即Y组合子

邱奇数除法

有了递归,我们实现邱奇数除法就变得顺理成章了。
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> d i v = Y λ d i v . λ m . λ n . ( ( l e s s m n ) 0 ( a d d ( d i v ( m i n u s m n ) n ) 1 ) ) div = Y\ \lambda div.\lambda m.\lambda n.((less\ m\ n)\ 0\ (add (div\ (minus\ m\ n)\ n)\ 1)) </math>div=Y λdiv.λm.λn.((less m n) 0 (add(div (minus m n) n) 1))

我们还可以顺道定义取余运算
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> m o d = Y λ m o d . λ m . λ n . ( ( l e s s m n ) ( m i n u s m n ) ( m o d ( m i n u s m n ) n ) ) mod = Y\ \lambda mod.\lambda m.\lambda n.((less\ m\ n)\ (minus\ m\ n)\ (mod\ (minus\ m\ n)\ n)) </math>mod=Y λmod.λm.λn.((less m n) (minus m n) (mod (minus m n) n))

总结

至此,我们已经实现了邱奇数的加减乘除,在这个过程中,我们还顺道实现了分支和循环(递归)两种逻辑,这样,我们推导出一组跟编程语言环境相近的lambda演算基础设施。

在整个学习过程中,你应该对一些函数式编程中常见的概念有一定体会:

  1. 函数是一等公民:在lambda演算中,函数是函数、函数是数据、函数是参数、函数是返回值、函数也是表达式,应该说,在原版lambda演算中,并不存在所谓"二等公民",一切公民都是函数。
  2. 高阶函数:在lambda演算中,并不存在"非高阶函数",一切函数都是以函数为参数、以函数为返回值的。
  3. 柯里化:在lambda演算中,并不支持多参数的函数,所以只能通过柯里化的形式表达多参数函数。
  4. 纯函数/不可变性:在lambda演算中,并不存在变量和赋值,所以更不可能改变变量的值。

实际上,因为lambda演算属于纯数学,它的设计并不在乎性能和人类编写方便,所以多数编程语言不会直接使用lambda的邱奇数、Y组合子等作为基础设施。

然而,正因为lambda演算达成了图灵完备,这让我们可以从其中获得制造计算机和设计编程语言的灵感。后世也在这个思路的基础上发展了组合子逻辑、类型论、基于范畴论的计算理论,这些都带来了编程领域的新发展。

相关推荐
再思即可4 天前
sicp每日一题[2.13-2.16]
编程·lisp·函数式编程·sicp·scheme
ezreal_pan18 天前
基于约束大于规范的想法,封装缓存组件
redis·缓存·函数式编程
知识分享小能手2 个月前
从新手到高手:Scala函数式编程完全指南,Scala 文件 I/O(27)
大数据·开发语言·后端·flink·spark·scala·函数式编程
程序员Allen2 个月前
第一个Lambda表达式
java·函数式编程
互联网架构小马2 个月前
12种增强Python代码的函数式编程技术
开发语言·后端·python·函数式编程
知识分享小能手3 个月前
从新手到高手:Scala函数式编程完全指南,Scala 访问修饰符(6)
大数据·开发语言·后端·python·数据分析·scala·函数式编程
Jack_hrx3 个月前
Java中的Monad设计模式及其实现
java·开发语言·设计模式·函数式编程·monad
niuiic4 个月前
来聊聊函数式
typescript·函数式编程
无休居士4 个月前
【设计模式】函数式编程范式工厂模式(Factory Method Pattern)
设计模式·函数式编程·工厂模式
陈建1115 个月前
设计模式学习笔记 - 开源实战三(下):借助Google Guava学习三大编程范式中的函数式编程
函数式编程