標籤:

在lambda表達式中如何使用遞歸?

或者說,如何能把一個匿名函數定義成遞歸函數?


自問自答,不明覺厲: Fixed-point combinator

.

=======================================

2014_02_03 更新

看了兩篇Y Combinator的文章, 大抵知道了Y Combinator是怎麼推出來的. 答案就更新一下, 算作一個學習筆記吧..

上面 胡鞍鋼 已經說到, 對函數, F(f) = lambda(n)((n == 0) ? 1 : n * f(n-1)), 階乘函數其實是F的不動點,即F(fact)=fact.

而Y是這樣一個函數, 輸入一個函數, 返回它的不動點函數.

它的定義是Y(F)=F(Y(F))

大家可以用這個定義下,如果支持lazy evaluation的話, Y(F)就是階乘函數的定義了.不過,

Y(F)=F(Y(F))這個定義, 仍然是匿名定義.

接下來使用scheme來說明如何匿名遞歸的定義Y

仍然以階乘函數定義為例,

下面用fact表示定義階乘函數的輔助函數, 用fac表示最終定義的階乘函數

首先, 我們會希望這樣做, 假定我們已經定義了階乘函數self, 我們用self來定義階乘函數self, 顯然我們可以直接調用self, 不過這裡我們將self函數作為fact的一個參數傳入, 如此定義:

(define (fact self n)
(if (= 0 n)
1
(* n (self (- n 1))))

然後如此計算階乘: (fact fact n)

不過這樣顯然是錯誤的, 因為fact要求兩個參數, 而self只要求一個參數.

於是我們修改代碼如下:

(define (fact self n)
(if (= n 0)
1
(* n (self self (- n 1))))

如此, 我們已經可以計算階乘了:

不過, 我們似乎更希望用(fact 5)這樣的形式, 於是將上面的代碼再修改為

(define (fact self)
(lambda (n)
(if (= n 0)
1
(* n ((self self) (- n 1))))))

於是...

我們看到, fact函數中沒有自由變數, 於是, 我們事實上已經定義了匿名遞歸的階乘函數, 可以將fact展開如下:

(define fac
((lambda (self)
(lambda (n)
(if (= n 0)
1
(* n ((self self) (- n 1))))))
(lambda (self)
(lambda (n)
(if (= n 0)
1
(* n ((self self) (- n 1)))))))

很容易發現, (f x)等價於((lambda (arg) (f arg)) x), 也可以轉換為:((lambda (F) (F x)) f)

於是, 我們修改fact函數的定義. fact原來的定義是:

(define (fact self)
(lambda (n)
(if (= n 0)
1
(* n ((self self) (- n 1))))))

修改為:

(define fact
(lambda (self)
((lambda (F)
(lambda (n)
(if (= n 0)
1
(* n (F (- n 1))))))
(self self))))
(define fac (fact fact))

這樣是可以正確運算的.

這時我們發現, 最開始提到過的 F(f) = lambda(n)((n == 0) ? 1 : n * f(n-1))

已經出現在這個fact的定義中了,

(define F*
(lambda (F)
(lambda (n)
(if (= n 0) 1
(* n (F (- n 1)))))))
等價於
F(f) = lambda(n)((n == 0) ? 1 : n * f(n-1))

如此, 再修改fact的定義

(define fact
(lambda (self)
(F* (self self))))
(define fac (fact fact))

我們發現這時用fac去計算(fac 3)時, 會陷入死循環. 這是因為有些沒有必要被求值的存在被求值了, 這裡不細說, 應該是lazy evaluation相關的. 不過沒關係, 剛才說過(f x) == ((lambda (y) (f y) x)

如此, 再修改fact的定義

(define fact
(lambda (self)
(F* (lambda (x) ((self self) x)))))

如此, 直接定義階乘函數fac

(define fac
((lambda (self)
(F* (lambda (x) ((self self) x))))
(lambda (self)
(F* (lambda (x) ((self self) x))))))

此時, 我們距離導出Y只有一步之遙了, 對上面fac的定義再做一下轉換, 上面這個fac的定義顯然等價於:

(define F*
(lambda (F)
(lambda (n)
(if (= n 0) 1
(* n (F (- n 1)))))))

(define fac
((lambda (F)
((lambda (self)
(F (lambda (x) ((self self) x))))
(lambda (self)
(F (lambda (x) ((self self) x))))))
F*))

定義Y

(define Y
(lambda (F)
((lambda (self)
(F (lambda (x) ((self self) x))))
(lambda (self)
(F (lambda (x) ((self self) x)))))))
(define fac (Y F*))

於是我們定義出了Y組合子, 可以試著用它匿名遞歸地定義其他函數, 比如斐波那契..

(define (Fib-Maker f)
(lambda (n)
(if (&<= n 1) 1 (+ (f (- n 1)) (f (- n 2)))))) (define fib (Y Fib-Maker))

推導用的參考:

Y Combinator

mvanier: The Y Combinator (Slight Return)


這就是大名鼎鼎的 Y Combinator 啊,匿名遞歸是可以實現的。

比如要求 n 的階乘,怎麼辦呢?

先看階乘的遞歸定義:

f(n) = (n == 0) ? 1 : n * f(n-1)

這個定義右側也用到了 f, 所以看起來得給 f 起個名字。其實有辦法繞過:

先定義這麼一個 (高階) 函數:

F(f) = lambda(n)((n == 0) ? 1 : n * f(n-1))

也就是說,F 的輸入是一個函數 f,輸出是另一個函數 F(f) = g, 這裡 g 滿足

g(n) = (n == 0) ? 1 : n * f(n-1)

這個和階乘的遞歸定義的差別在於,左邊是 g, 右邊是 f, 這完全可以是兩個不同的函數。但如果 g 恰好等於 f, 那麼 g(也就是 f)就是階乘函數。所以,匿名地定義階乘函數,要點在於解這麼一個方程:

F(g) = g

也就是求 (高階) 函數 F 的一個不動點。這正是 Y Combinator 乾的事情。

Scheme 里的定義:

(define Y
(lambda (F)
((lambda (x) (x x))
(lambda (g)
(F (lambda args (apply (g g) args)))))))

(args 那個部分並不重要,那個只是允許函數取多個變數)

現在大家可以試試,如果在 Scheme 里定義了上述高階函數 F, 那麼現在

&> ((Y F) 5)
120
&> ((Y F) 6)
720
&> ((Y F) 7)
5040

Y 滿足 F (Y F) = Y F, 所以 Y F 是 F 的一個不動點(即 F(g) = g 的解),可以用歸納法證明(或者從上面的解釋看出)這個就是階乘函數


在SICP裡面的一道練習題也有介紹:http://mitpress.mit.edu/sicp/full-text/book/book-Z-H-26.html#%_thm_4.21


更新:今天確實擴展了知識。求 5 的階乘,翻譯成 Lua 代碼:

a = (function(n)
return (function(fact)
return fact(fact, n)
end)(function(ft, k)
return k == 1 and 1 or (k * ft(ft, k-1))
end)
end)(5)

更新:在滿足一些要求的語言中可以採用 fixed-point combinator。謝謝 @胡鞍鋼 指正。

唯一的方式是將這個 lambda 表達式付給一個名字(變數)。任何其它方式都僅僅是讓這個名字更局部化而已。


作為自己的參數傳遞進去, 在內部調用即可. 模式如下:

(f, x) =&> x &<= 1 ? 1 : x * f(f, x - 1);


我的第一反應,是 arguments.callee 一定是哪裡不對。


推薦閱讀:

Python 中的 lambda 和「真正的」lambda 有什麼區別?
Lisp的精髓是什麼?

TAG:編程 | Lisp | Scheme |