尝试理解Y-combinator
简介
在一个纯粹的函数式语言环境中,只有一种元素,就是一个单参函数
;; id function (lambda (x) x)
-- id function in haskll \x -> x
问题
比如需要求解一个列表的长度,该如何实现。
算法规则很简单: > 1. 空列表长度为0 > 2. 列表长度等于 1 + 列表除了头部元素以外的部分
常规的算法实现如下:
common lisp
;; in common lisp
(defun len (ls)
(if (null ls)
0
(1+ (len (cdr ls)))))
CL-USER> (len '(1 2 3)) 3 CL-USER> (len '()) 0
haskell
len1 :: (Num b ) => [a] -> b len1 [] = 0 len1 (_:xs) = 1 + len1 xs
λ> len1 [] 0 λ> len1 [1,2,3] 3
但是,如何基于纯粹的lambda表达式实现呢?
第一次尝试
尝试翻译之前写过的实现
(lambda (ls)
(if (null ls)
0
(1+ (?? (cdr ls)))))
*怎么办*,=?= 部分无法填充。因为现在这个匿名函数木有名字
神来一笔
假如有一个len的实现,不就可以了么!!!!
(lambda (perfectLenFunc)
(lambda (ls)
(if (null ls)
0
(1+ (perfectLenFunc (cdr ls))))))
哇擦,要是有=perfectLenFunc=(后续简写为=plf=…),还在这里浪费时间干神马。
*咳咳*,不急
;; change to scheme, due to the function namespace
(define len1
((lambda (plf)
(lambda (ls)
(if (null? ls)
0
(1+ (plf (cdr ls))))))
(lambda (ls)
(if (null? ls)
0
(1+ (error (cdr ls)))))))
scheme@(guile-user)> (len1 '()) $3 = 0 scheme@(guile-user)> (len1 '(a)) $4 = 1 scheme@(guile-user)> (len1 '(a b)) ERROR: In procedure scm-error: ERROR: ()
咦,这样就可以支持长度为0的列表和长度为1的列表咧。
整体优化一下重复代码
(define mk-len
(lambda (plf)
(lambda (ls)
(if (null? ls)
0
(1+ (plf (cdr ls)))))))
(define len0 (mk-len error))
(define len1 (mk-len len0))
(define len2 (mk-len len1))
;; output
scheme@(guile-user)>
(define len0 (mk-len error))
(define len1 (mk-len len0))
(define len2 (mk-len len1))
scheme@(guile-user)> (len2 '(1 2))
$6 = 2
看来,功夫不负有心人,只要足够努力, 不管多长的列表,都能写出对应的函数算出来! > 太天真了少年 (画外音)
神又来一笔
时间过去了一年,少年终于写出了可以计算长度 =14239823586=以内的列表的长度!
突然一个霹雳从天而降 > 你个XX,想写到死啊!!!! (画外音again)
咦,注意 (define len0 (mk-len error)),
=error=耶,岂不是说不管提供神马函数,都 不影响么
(define len2 (mk-len (mk-len (mk-len mk-len)))) ;; output scheme@(guile-user)> (define len2 (mk-len (mk-len (mk-len mk-len)))) scheme@(guile-user)> (len2 '(1 2)) $8 = 2
哇,那岂不是可以这样!!!
(define real-len
((lambda (mk-len)
(mk-len mk-len))
(lambda (mk-len)
(lambda (l)
(if (null? l) 0
(1+ ((mk-len mk-len) (cdr l))))))))
scheme@(guile-user)> (real-len '(1 2 3 a b d c s))
$9 = 8
好棒!好陶醉!好满足!!!
神又来一笔!
不过,写出来的程序看着好奇怪。好多=mk-len=,=(mk-len mk-len)=, 看不懂啊. 只有
(lambda (l)
(if (null? l) 0
(1+ (?? (cdr l))))))))
这个才是我想要的呢… 那就想办法把=(mklen mklen)= 搞出去,做参数传进来好了👌
((lambda (mk-len)
(mk-len mk-len))
(lambda (mk-len)
((lambda (len)
(lambda (l)
(if (null? l) 0
(1+ (len (cdr l))))))
(lambda (x) ((mk-len mk-len) x)))))
哇,中间的代码看起来,有点像那么一回事了。想办法挪挪结构,更好看一点。
((lambda (len')
((lambda (mk-len) (mk-len mk-len))
(lambda (mk-len) (len' (lambda (x) ((mk-len mk-len) x))))))
(lambda (len)
(lambda (l)
(if (null? l) 0
(1+ (len (cdr l)))))))
BINGO !!
the ultimate Y-Combinator
(define Y
(lambda (targetFunction)
((lambda (f) (f f))
(lambda (f) (targetFunction (lambda (x) ((f f) x)))))))
(define len
( Y (lambda (len')
(lambda (l)
(if (null? l) 0
(1+ (len' (cdr l))))))))
;; output
scheme@(guile-user)> (len '(a b d c dd s sf ad f))
$10 = 9
炫酷爆棚了! 有没有
实践
in haskell
-- here is where miracle begins
newtype Rec a = In { out :: Rec a -> a } -- for type deduction
y :: (a -> a) -> a
y tf = (\f -> out f f) $In (\f -> tf (out f f))
ylen :: (Num b) => [a]->b
ylen = y (\len' ls -> if null ls then 0 else (len'.tail$ls)+1)
ysum :: (Num a) => [a] -> a
ysum = y (\sum' ls -> if null ls then 0 else head ls + (sum'.tail $ ls))
-- output
λ> ylen [1,2,3,4]
4
λ> ysum [1,2,3,4]
10