CS61A-lab03-Q6_ChurchNumeral丘奇数
本文最后更新于:2023年11月25日 晚上
Q6: Church Numerals
The logician
Alonzo Church
invented a system of representing non-negative integers entirely using functions. The purpose was to show that functions are sufficient to describe all of number theory: if we have functions, we do not need to assume that numbers exist, but instead we can invent them.Your goal in this problem is to rediscover this representation known as
Church numerals
.Church numerals
are a way to represent non-negative integers via repeated function application. Specifically, church numerals ( such aszero
,one
, andtwo
below ) are functions that take in a functionf
and return a new function which, when called, repeatsf
a number of times on some argumentx
. Here are the definitions ofzero
, as well as asuccessor
function, which takes in a church numeraln
as an argument and returns a function that represents the church numeral one higher thann
:
1 |
|
First, define functions
one
andtwo
such that they have the same behavior assuccessor(zero)
andsuccesssor(successor(zero))
respectively, but do not callsuccessor
in your implementation.Next, implement a function
church_to_int
that converts a church numeral argument to a regular Python integer.Finally, implement functions
add_church
,mul_church
, andpow_church
that perform addition, multiplication, and exponentiation on church numerals.
Solution
1 |
|
Analysis
根据我们对zero
和successor
两个函数的观察,zero
就相当于自然数0
的定义,而successor
实际上相当于一个递增函数。
我们要做的,就是以这个0
的定义为起点,定义出所有自然数以及他们的基础运算。
题目中提到,函数one
的行为应该和successor(zero)
的行为一致,也就是说:
1 |
|
观察successor
,可以仿照其结构写出one
函数和two
函数:
1 |
|
其实,zero(f)(x)
就是x
,而one(f)(x)
就是f(x)
,所以我们也可以这样写:
1 |
|
由successor
的定义,我们可以发现自然数N
对应的函数n
的定义应当为:
n(f) = f((n - 1)(f))=f(f(f(f(f(x)))))
#n个
f
如果我们按照这样的定义方式,定义自然数n
:
1 |
|
church_to_int
church_to_int
本质上就是计数嵌套的 f
有多少个。每走一层就给计数变量加一。
考虑将 f
设置为自增函数 increment
。这样,传入的参数值就是计数变量的初值,函数的返回值就是终值了。
1 |
|
事实上,有了n(f)(x)
就是n
的思想以后,用平常思维就可以写出答案正确的解:
1 |
|
显然,这不会也不应该是最佳答案
add_church
我们把m
和n
各自看成整体。m
是m
个f
函数嵌套的整体,可以看成是关于函数f
的函数,n
是n
个f
函数嵌套的整体.
当m
和n
传入的参数f
相同时,我们要求m+n
,即想办法获得一个m+n
层的高阶函数.
用数学表达式为:f(f(f(.....[f(f(f(x)))].....)))
,内部的括号为n
层f嵌套,外层为m
层括号嵌套。
考虑计算 m+n
,也就是把 m+n
个 f
套在一起。而我们知道m(f)
可以实现 m
次嵌套,n(f)
可以实现 n
次嵌套。
那么,我们在n(f)
外面套一个m(f)
即可:
1 |
|
mul_church
计算 n×m
也就是 m
个 n
相加,我们需要先把函数f
嵌套n
次,再把嵌套之后得到的n
函数值嵌套m
次。
这一次对于m
函数而言,它的参数不再是函数f
,而是嵌套之后的函数值n
。
换言之,即 n(f)
自己套自己套上 m
次:
1 |
|
pow_church
m(f)
的功用是将 m
个 f
嵌套起来,那么 n(m(f))
的功用也就是将 n
个m(f)
嵌套起来,即
1 |
|
FAQ
m x n
写成:lambda f: m(n(f))
, m^n
写成:lambda f: n(m)(f)
。这两者看起来非常非常相似,为什么结果相差这么大呢?
最简单的解释就是作用对象不同。
先来看第一条:
lambda f: m(n(f))
。把m
和n
分别看成是一个整体,在这行代码当中,n
接收了一个参数f
。而m
接收的参数是n(f)
。我们可以做一个换元,我们假设g = n(f)
,那么m
函数的入参就是g
。再来看第二条代码:
lambda f: n(m)(f)
,这里m
函数没有入参,而n
函数的入参是m
。表面上一个入参是
n(f)
一个是m
,好像差不多。其实相差很大。
n(f)
是n
函数作用于f
上的结果,这个结果不再具备将函数嵌套n
层的能力。所以外层再套上m
函数,得到的也只是这个结果本身嵌套m
次。而
n(m)
不同,传入的参数是m
本身,而非m
作用之后的结果,m
函数的功能就是将输入的函数嵌套m
次,所以当它经过n
函数作用于自身的时候,总层数会不停地乘上m
。