Y combinator本身是一个无状态函数,当用于另一个无状态的函数时,将返回该函数的递归版本。比如定义Y:(为了说明方便,用λi代替lambda关键字)
(defun y (f)
((λ1 (x) (funcall x x))
(λ2 (y)
(funcall f (λ3 (&rest args)
(apply (funcall y y) args))))))
用计算Factorials的函数为例子:
(defun fac (f)
(λ4 (n)
(if (zerop n)
1
(* n (funcall f (1- n))))))
那么(y #'fac)代表什么呢?
这里约定(A B)表示以B为参数调用A。以#'fac为参数进入y后,发生如下调用链:
(λ1 λ2) -> (λ2 λ2) -> (fac λ3) -> λ4
所以,(y #'fac)其实是一个以整数为参数的函数,进入REPL试试:
>(funcall (y #'fac) 5)
120
分析正确。那么是怎样得到120的呢? 从λ4继续:
(λ1 λ2) -> (λ2 λ2) -> (fac λ3) -> λ4
| |
| <- (λ3 n-1) <-No- 参数n为0? -Yes-> 结束
所以这个Y版的fac虽然没有直接调用自己,但在Y的帮助下还是间接地调用了自己。
随便看看其它版本的factorial实现:
递归版:
(defun fac (n)
"一般递归"
(if (< n 2)
1
(* n (fact (- n 1)))))
(defun fac (n)
"尾递归"
(labels ((rec (n accum)
(if (zerop)
accum
(rec (1- n) (* n accum)))))
(rec n 1)))
(defun fac (n)
"仅供演示"
(funcall
(lambda (fn n)
(funcall fn n fn))
(lambda (n this)
(cond
((> n 0) (* n (funcall this (- n 1) this)))
(t 1))) n)
迭代版:
( loop for result = 1 then ( * result i)
for i from 2 to n
finally ( return result)))
函数版:
( defun fac (n) ( reduce #'* ( loop for i from 1 to n collect i)))
试试(fac 30000), 在ECL中也就几秒时间! 够强悍吧。(在Ruby, Python中可能早就溢出了)
可能很少有程序员用Y版来实现这样的函数, Y版的意义在于能让你更好地理解高阶函数,函数作为参数/返回值这些基本的FP概念。