对Lambda演算的再学习

by Albert Lee

09/13/2006

Home

Python

   正在读一系列的文章,对 lambda, High-order function, lazy-eval 等重要的FP概念作进一步的学习。参考连接:
http://blog.csdn.net/g9yuayon/category/16773.aspx  
http://blog.csdn.net/g9yuayon/archive/2006/05/29/759778.aspx
限于本人数学基础趋近于0,文中不当及不严格之处敬请指正.
   

    Lambda Calculus 在数学定义上面非常简单,并且和与图灵机等价,也就是说FP语言程序与普通的命令式语言如C可以写出同样能力的程序。lambda算子理论里所有东东都是函数.

Lambda 定义

    一个 Lambda 定义就是一个: 
        lambda <参数> : <函数体> 
    这个定义可以应用到参数上,进行求值。

例(py):
>>> (lambda x : x + x)(5)
10

Currying

    在数学定义中 lambda 只有一个参数,我们可以通过 Currying 来实现组合多个参数的能力:

例:
>>> (lambda x : (lambda y : x + y))(1)(2)
3

    这里面所包含的思想是“函数本身也是数据,也是值,可以被传递,可以被计算!",在上面的例子中, "第一个函数接受一个参数并返回另一个接受另一个参数的函数",这听起来有些绕,我们分解来看:

    lambda x :  接受一个参数x, 这个定义返回如下的值:
           (lambda y: x + y)  这个值是另一个lambda定义,它接受另一个参数y.

我们平常使用的 lambda x, y : x + y  就是上面 currying 这个工具的简单用法.

Closure

    终于遇到 Closure 这个词了.closure在我自己的理解就是表示数据的层次性.
    closure我曾经见到有本书翻译成"闭包",封闭(closure),或者叫完全有界(complete bounding)。这里的有界和一阶逻辑谓词中的有界是一个意思.
    比如 (lambda x : x + x) , x出现在 lambda x : ....的参数表中, x 就被包含在这个lambda的  context 里面. 再比如前面 currying 的那个例子:
    (lambda x : (lambda y : x + y))  y 出现在内层的 lambda 中,它的 context就是内部的那个lambda.  而没有出现在 lambda .. 这里的变量就是自由变量.在这里内部的  lambda y : x + y 中, x 是一个自由变量, y 是有界变量. 在整个  lambda x : ... 的定义中,x,y 都是有界变量.

例:

>>> y = 2
>>> (lambda x : x + y)(4)
6

y 没有出现在 lambda 的参数表中,这里它绑定的是外部context中的 y, 是一个自由变量.

另一个例子:
>>> a = 1
>>> (lambda a : a + 1)(2)
3

这里, lambda 中的a 与外界的a 不同,因为context不同.

当一个 lambda表达式的所有变量都有界时,此表达式才完全合法.

从这个意义上讲,整个运行环境可以看作是一个大的 lambda表达式, 所有的函数以及变量,肯定要定义在某一个层次上.

lambda算子计算规则:   alpha, beta

alpha 转换

    就是说lambda表达式中的参数名称可以随意替换, 名字本身不重要.通过alpha转换可以实现递归等操作.
例:
lambda x, y : x + y

lambda a, b : a + b 完全等价.

beta简化

Beta规则无非是说,应用一个函数(也就是lambda表达式。一个意思)等价于通过把函数体内有界的变量替换成应用里对应参数的实际值来替换原来的函数。


例:  (lambda x, x + 2) (1)   相当于把 1 替换掉x, 于是得到: 1 + 2

另一个例子:
>>> f = (lambda y : (lambda x : x + y))(5)
>>> f(2)
7
>>> f(1)
6
这里应用一个函数生成了一个 add5 的函数, 动态语言的强大哦, :D
上面的例子,相当于生成了:   lambda x : x + 5 这样一个表达式.

再看一个例子:

>>> (lambda x, y : x(y))(lambda x : x * x, 3)
9
>>>

这个例子中, lambda x, y  将x 应用到 y 上. 其中 x 替换成  lambda x : x * x , y 替换成 3.
Beta的严格定义如下:

lambda x . B e = B[x := e] if free(e) \subset free(B[x := e]

这条规则是为了保证出现命名冲突的时候,先进行 alpha 替换,然后再应用 beta 简化.


    至此, 通过 lambda , currying, closure, alpha, beta 已经定义出一个"完备"的计算体系. 在此之上,我们可以继续构造出更复杂的程序.


丘奇数对数字的定义


FP里面只有函数,没有其他^ ^ 数字其实也是函数.

丘齐数里,所有的数字都是两个参数的函数:

  1. 零是 lambda s z . z
  2. 一是 lambda s z . s z
  3. 二是 lambda s z . s (s z)
  4. 任意数n,就是将 s 应用到 z 上n次.
这里可以把 z 看作 zero, s 看作 succeed 函数.

Church 数把数字看作是函数的应用, 一个数字n 就是"数"5下. 至于它的理论价值,据说很牛,我也不大了解,略了.


现在,有了基本函数概念与变换规则,以及数的概念,还缺少"分支选择" , "重复操作(递归)”


选择结构

Boolean值的定义


boolean值同样是通过函数定义生成的:
>>> T = lambda x, y : x
>>> F = lambda x, y : y

    逻辑操作的定义, 这个定义实在是太美妙了~ 不得不令人感叹数学之美,世间复杂万物都可以由简单的概念演绎生成。

逻辑与:

>>> BoolAnd = lambda x, y : x(y, F)
>>> BoolAnd(T, F)
<function <lambda> at 0x00CD0A30>
>>> F
<function <lambda> at 0x00CD0A30>
>>>
BoolAnd 的两个参数是 T 或 F,当然,他们都是函数,返回的结果也是一个函数。 上面的例子中, T && F 返回结果为 F (是一个函数,这个函数的地址与 F 相同,因此确认为F)。或者用下面的方式来检验:
>>> T == BoolAnd(T, T)
True
>>> F == BoolAnd(T, F)
True
>>> F == BoolAnd(F, T)
True
>>> F == BoolAnd(F, F)
True
>>>

逻辑或:

>>> BoolOr = lambda x, y : x(T, y)
>>> BoolOr(T, F)
>>> T == BoolOr(T, F)
True
>>> F == BoolOr(F, F)
True
>>> T == BoolOr(T, T)
True
>>> T == BoolOr(F, T)
True
>>>

逻辑非:

>>> BoolNot = lambda x : x(F, T)
>>> F == BoolNot(T)
True
>>> T == BoolNot(F)
True
>>>

If-Then-Else

基本分支程序,程序结构同样是函数定义-_-!
>>> IfThenElse = lambda cont, T_expr, F_expr: cont(T_expr, F_expr)
>>>
>>> IfThenElse(T, 1, 2)
1
(一个问题, 我们定义的 T, F 如何与 Python 原生的 true, false 通用呢? 比如,如何写:  IfThenElse(3>2, 1, 2) 这样的? )

用一个比较 dirty 的方法来解决这个问题:

>>> def  BOOL(b):
    if b: return T
    else: return F

   
>>> IfThenElse(BOOL(3>2), 1, 2)
1
>>>

(ok, 我所参考的中文blog翻译就到此为止了,实在等不下去了,自己啃英文原文去了 - 9/12/06)
http://goodmath.blogspot.com/2006/06/lamda-calculus-index.html

Why oh why Y?

    前面从底向上构造了数字,bool,选择结构,现在还缺少能够重复执行的机制。在lambda演算中实现重复的方式是“递归”,但是 lambda表达式没有名字,只有函数,于是用 Y combinator 的技巧。Y combinator 是 lambda 表达式的不动点操作符~
(It's called the Y combinator, aka the lambda fixed point operator)

简单的递归的例子,当然是我最爱的 Factorial 的定义:
形式定义:
factorial(n) = 1 if n = 0
factorial(n) = n*factorial(n-1) if n > 0

<erlang>:
factorial(0) -> 1;
factorial(N) -> N * factorial(N-1).

<python>:
def factorial(N):
    if N == 0:
       return 1
    else:
       return N * factorial(N-1)

Y combinator 是一个高阶函数,它的定义如下:

let Y = lambda y . (lambda x . y (x x)) (lambda x . y (x x))

<py>
>>> Y = lambda y:(lambda x : y(x(x)))(lambda x:y(x(x)))
>>>


……看上去好复杂啊。原文有一个树型的 lambda 图,可以很好的解释为什么这个操作叫做 Y ,因为看上去这个表达式就是一棵倒置的树。
这个表达式的特别之处是: 将其应用于本身会得到本身…… (好绕)
(Y Y) = Y (Y Y)
我把python的那个执行了:  Y(Y) 结果是死循环,这个结果看来是不对的?

>>> Y(Y)

Traceback (most recent call last):
  File "<pyshell#68>", line 1, in -toplevel-
    Y(Y)
  File "<pyshell#67>", line 1, in <lambda>
    Y = lambda y:(lambda x : y(x(x)))(lambda x:y(x(x)))
  File "<pyshell#67>", line 1, in <lambda>
    Y = lambda y:(lambda x : y(x(x)))(lambda x:y(x(x)))

把 Y 进行一系列的展开,alpha,beta转换,最终 (Y Y) = Y (Y Y)
That's the magic of Y: it reproduces itself. (Y Y) = Y (Y Y) = Y (Y (Y Y)), forever. Y combinator 的作用就是生成了自身,然后自身再生成自身,直到永远~~ 这就是上面的程序死循环的原因.顺便想到,这个 Y 定义的形式非常像 Lisp 中打印自身的那个程序, 这也可能就是为什么很多人热衷于编写打印自身的程序的一个深层次背景.


等等!! 忽然有点灵光!我们是需要重复执行的能力,现在我们得到了一个死循环的结果, 我们可以重复的执行一些东西了~!


通过Y组和可以实现递归,至于具体的细节,这里做个记号,继续研究.

Lazy evaluation

(lambda x y . x * y) 3 ((lambda z. z * z) 4)

对于这样的组合表达式,有两种展看的方式:

(1) 3 * ((lambda z. z*z) 4)

(2) (lambda x y. x*y) 3 (4 * 4)

    两种方式结果一样,但是有时候会有区别.第一种方式称为 lazy evalutiaon, 延迟求值, 第二种方式称为eager evaluation。 高阶函数与延迟求值是 FP的两个强有力的工具。

    在实际语言中 Lisp, Scheme, ML使用 eager evaluation, Haskell,Miranda使用 Lazy evaluation。

Combinatiors  

combinator 是组合器,前面介绍了Y combinator, 下面再说几个:
>>> S = lambda x, y, z: x(z)(y(z))
>>> K = lambda x: lambda y: x
>>> I = lambda x:x
S: S is a function application combinator;
K: K generates functions that return a specific constant value
I: I is the identity function

一个使用例子:
>>> S(K, K, 1)
1
>>> S(K,K,"hello")
'hello'

    S(K,K, x) 这个组合,实际上就是 I 所完成的功能,也就是说通过 S K 来组合便实现了 I的一个等价物。据说,我们可以通过S K两个 combinator便实现所有 lambda 算子的等价物!! 太不可思议了。