CS61A Homework: Church Numerals

Jane_leaves發表於2020-10-15

Church Numerals


Nagging

南大的 SICP 實際上是 Berkeley CS61A 的 clone ,所以我有幸做到了這個 Homework02。

此外要感謝選課系統,讓我一個工科學生也能有幸享受世界一流大學的 CS 課程。

今天是 SICP 的 Lecture 5 ,這些 higher-order function 的內容完全是我的知識盲區。可見我覺得自己稍微有點的那些水平充其量也就是百川灌河罷了。

南大給的講義上說:

This section is out of scope for our course, so the problems below is optional.
That is, the problems in this section don't count for your final score and don't have any deadline.
Do it at any time if you want an extra challenge or some practice with high order function and abstraction!

既然都不計成績,我覺得程式碼應該是能放到網上的。因此有了這篇文章。

P.s. 問過助教老師了,是可以發上網的


Homework

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. Here are
the definitions of zero , as well as a function that returns one more than its argument:

def zero(f):
return lambda x: x
def successor(n):
return lambda f: lambda x: f(n(f)(x))

First, define functions one and two such that they have the same behavior as successor(zero)
and successor(successor(zero)) respectively, but do not call successor 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 , and pow_church that perform addition,
multiplication, and exponentiation on church numerals.

##########################
# Just for fun Questions #
##########################
HW_SOURCE_FILE = 'lab02.py'

from operator import add, mul, sub

square = lambda x: x * x

identity = lambda x: x

triple = lambda x: 3 * x

increment = lambda x: x + 1

def zero(f):
    return lambda x: x

def successor(n):
    return lambda f: lambda x: f(n(f)(x))

def one(f):
    """Church numeral 1: same as successor(zero)"""
    "*** YOUR CODE HERE ***"

def two(f):
    """Church numeral 2: same as successor(successor(zero))"""
    "*** YOUR CODE HERE ***"

three = successor(two)

def church_to_int(n):
    """Convert the Church numeral n to a Python integer.

    >>> church_to_int(zero)
    0
    >>> church_to_int(one)
    1
    >>> church_to_int(two)
    2
    >>> church_to_int(three)
    3
    """
    "*** YOUR CODE HERE ***"

def add_church(m, n):
    """Return the Church numeral for m + n, for Church numerals m and n.

    >>> church_to_int(add_church(two, three))
    5
    """
    "*** YOUR CODE HERE ***"

def mul_church(m, n):
    """Return the Church numeral for m * n, for Church numerals m and n.

    >>> four = successor(three)
    >>> church_to_int(mul_church(two, three))
    6
    >>> church_to_int(mul_church(three, four))
    12
    """
    "*** YOUR CODE HERE ***"

def pow_church(m, n):
    """Return the Church numeral m ** n, for Church numerals m and n.

    >>> church_to_int(pow_church(two, three))
    8
    >>> church_to_int(pow_church(three, two))
    9
    """
    "*** YOUR CODE HERE ***"

Solution

很厲害的題目。我是第一次以這種角度思考問題,這種體驗令人很興奮。

首先考慮補完 onetwo 兩個函式。

按照 zerosuccessor 的定義,我們很容易就能不動腦子地寫出程式碼。當然,題目原本應該並非這個意思。

考慮稍微畫兩下,容易得到這樣的程式碼:

def one(f):
    return lambda x: f(x)

def two(f):
    return lambda x: f(f(x))

successor 的定義,發現數字 \(N\) 對應的函式 \(F_N(f)\) 的定義應當為:

\[F_N(f) = f(F_{N-1}(f)) = \dots = \underbrace{f(f(f(\dots(f}_{N個})))) \]

也就是說,這裡的數字 \(N\) 實際上表示巢狀的 \(f\) 個數。很容易用歸納法證明。


接下來考慮 church_to_int 的實現。

容易發現 church_to_int 本質上就是計數巢狀的 \(f\) 有多少個。要數數,當然就是要逐層把函式巢狀走一遍了,每走一層就給計數變數加一。那麼考慮將 \(f\) 設定為自增函式 increment 。這樣,傳入的引數值就是計數變數的初值,函式的返回值就是終值了。

def church_to_int(n):
    return n(increment)(0)

然後是 add_church 。要實現這個函式當然可以一個迴圈跑下來。但是,那不夠美。

這三個單行函式寫下來,你還好意思用長篇大論去實現某個小功能嗎?顯然否。

考慮計算 \(m + n\) ,也就是把 \(m + n\)\(f\) 套在一起。而我們知道 \(F_m\) 可以實現 \(m\) 次巢狀, \(F_n\) 可以實現 \(n\) 次巢狀,我們在 \(F_n\) 外面套一個 \(F_m\) 即可,那麼就有:

def add_church(m, n):
	return lambda f: lambda x: m(f)( n(f)(x) )

接下來考慮實現 mul_church ,計算 \(n \times m\) 也就是 \(m\)\(n\) 相加,

換言之,要把 \(F_n\) 自己套自己套上 \(m\) 次,程式碼非常簡單:

def mul_church(m, n):
	return lambda f: m(n(f))

最後考慮 pow_church ,這其實是最富技巧性的一個,或許也是最簡單的一個。

\(F_m(f)\) 的功用是將 \(m\)\(f\) 巢狀起來,那麼 \(F_n(F_m(f))\) 的功用也就是將 \(n\)\(F_m\) 巢狀起來,即

\[\begin{align*} F_n(F_m(f)) &= \underbrace{F_m(F_m(F_m(\dots(F_m}_{n個})))) \\ &= \overbrace{\underbrace{f(f(f(\dots(f(\underbrace{f(f(f(\dots(f(\dots \underbrace{f(f(f(\dots \dots \dots(f}_{m個})))))}_{m個})))))}_{m個}}^{n個})))) \end{align*} \]

這正是乘方的定義。

def pow_church(m, n):
    return n(m)

至此做完了。

其實寫部落格的時候就會發現許多事情想明白了卻說不明白,實在是我水平有限。

做完這樣一道題目讓人充滿了成就感。從 花一小時寫出來的五六行程式碼 與 從幾小時碼出的幾百行資料結構 獲得的成就感是不一樣的。相較而言,前者似乎更具有一種茅塞頓開的快感。