尝试理解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