• 如何猜出 Y combinator


    先约定几个记号:

    1. 定义用一个冒号加等号表示“:=”,
    2. 表达式全等用两个等号表示“==”,
    3. 归约意义上的相等用一个等号表示“=”,“==”蕴涵“=”。

    由于lambda演算不能定义符号,所以像这样的递归定义是不能被求值的:

    f := (lambda () (f))

    如何在lambda验算中实现递归?从最简单的递归函数开始。希望能带来一些启发。

    寻找 (Omega)

    首先,我们的目标是找出一个无限循环的lambda表达式。另外额外要求这个表达式最短。

    一个lambda表达式只可能有三种形式:

    1. 符号(x, y, z等等),
    2. 函数(如(lambda (x) x)),
    3. 应用(英文application,即函数调用,如调用函数func,参数x写作:(func x))。

    第1、2种形式又称为值(value),因为他们不能被归约了。一个无限循环的表达式只能是第3种形式:

    (exp1 exp2)

    考虑exp1。exp1是个函数,或者能被归约成函数的应用。如果exp1是应用,那么exp1要么会归约到一个函数,要么无限循环。如果exp1无限循环,就和我们的最短假设矛盾,所以exp1会归约到一个函数。如果exp1会归约到一个函数,那么和exp1是一个函数没什么区别。简单考虑,假设exp1是个函数:

    exp1 := (lambda (x) body)

    那么

    (exp1 exp2)
    == ((lambda (x) body) exp2)
    =  body[x <- exp2]

    为了让表达式无限循环,我们希望:

    (exp1 exp2) == body[x <- exp2]

    所以body应该是个应用:

    body := (subexp1 subexp2)
    <=> (exp1 exp2) == (subexp1[x <- exp2] subexp2[x <- exp2])
    <=> exp1 == subexp1[x <- exp2]  and  exp2 == subexp2[x <- exp2]

    从最后一个条件可以推出:

    exp2 == subexp2[x <- exp2]  <=>  subexp2 == x

    另外还能看出,表达式exp1中包含了一个和exp2相等的子表达式。满足这个条件的最简单的exp1就是和exp2相等的exp1。

    exp1 == exp2
    <=> subexp1[x <- exp2] == exp2
    <=> subexp1 == x

    综合几个条件:

    exp1
    == (lambda (x) body)
    == (lambda (x) (subexp1 subexp2))
    == (lambda (x) (x x))
    
    exp2
    == exp1
    == (lambda (x) (x x))

    于是我们要找的无限循环的表达式是:

    ((lambda (x) (x x)) (lambda (x) (x x)))

    这个表达式就是lambda演算里的(Omega)表达式。(Omega)表达式循环的关键在于(f f)形式的表达式,即自己应用到自己。(Omega)包含了三个(f f)形式的表达式。

    寻找 Y combinator

    一般来说,递归函数长这个样子:

    target := (lambda args body[target])

    body[target]的意思是一个包含target这个符号的表达式,args表示这是一个任意个数参数的函数。在这个表达式中,target是一个无约束的符号。一个包含无约束符号的表达式不能被求值。Lambda演算中约束一个符号的方法是用lambda关键字,为了让target变成约束符号,我们改写表达式:

    p := (lambda (target)
           (lambda args body[target]))
       = (lambda (f)
           (lambda args body[f]))

    为了避免混淆,上面将target改名成f((alpha)归约),target仍然表示我们想要的递归函数的表达式。target和p有如下关系:

    (p target)
    = (lambda args body[target])
    = target

    顺便一提,可以看出我们在寻找的target是p的一个不动点。

    接下来考虑如何找target。记得寻找(Omega)的启发吗?我们猜测target是一个(g g)形式的表达式:

    target == (g g)
    =>
      (p target) = target
      <=> (p (g g)) = (g g)
      <=> (g g) = (p (g g)) = ((lambda (x) (p (x x))) g)

    根据最后一个条件可以看出:

    g := (lambda (x) (p (x x)))
    =>
      target == (g g)
      == ((lambda (x) (p (x x)))
            (lambda (x) (p (x x))))

    把p提取到参数位置:

    target = 
    ((lambda (f)
       ((lambda (x) (f (x x)))
        (lambda (x) (f (x x))))) p)
    
    Y :=
    (lambda (f)
      ((lambda (x) (f (x x)))
       (lambda (x) (f (x x)))))
    
    target = (Y p)

    这个Y就是Y combinator。

    用 Y combinator 推导出 (Omega)

    最简单的无限循环函数是:

    f := (lambda () (f))

    利用Y combinator来生成这个函数的lambda表达式看看会得到什么?

    (Y (lambda (f) (lambda () (f))))
    = ((lambda (x) ((lambda (f) (lambda () (f))) (x x)))
       (lambda (x) ((lambda (f) (lambda () (f))) (x x))))
    = ((lambda (x) (lambda () ((x x))))
       (lambda (x) (lambda () ((x x)))))

    呼,要对齐这么多括弧真不容易。注意到((eta)归约):

    (lambda () ((x x))) = (x x)

    所以:

    (Y (lambda (f) (lambda () (f))))
    = ((lambda (x) (x x)) (lambda (x) (x x)))
  • 相关阅读:
    常见算法之10---从第一字符串中删除第二个字符串中所有的字符
    常见算法之9---折半查找(二分查找)
    常用算法之8---找到数组中只出现一次的数字
    常见算法之7---判断是否所有小字符串里的字母在大字符串里都有
    常见算法之6---判断集合S之中是否存在两个数之和为指定大小N
    常见算法之5---单例模式[java]
    常见算法之4---正整数二进制表示中的1的个数
    常见算法之3---数组中出现次数超过一半的数字
    UVa 495
    《编程之美》笔记(一)
  • 原文地址:https://www.cnblogs.com/skabyy/p/3426375.html
Copyright © 2020-2023  润新知