Modest opinions  
by a humble autodidact
学习计算机编程的时候不可避免的要接触到lambda calculus,为了更容易的掌握这个抽象模型,我用Ruby写了一个库Rambda,把lambda expression解析成syntax tree,这样就可以方便的编程操作lambda expression,实现各种reduction,对程序员来说,动手实践是最好的掌握概念的方式,对吧?

首先介绍本库支持的lambda express语法。用BNF描述,基本的语法是这样的:
<exp> ::= 
<exp>表示一个lambda express
          ^<var>.<exp>

构建一个函数,变量名是<var>(一个字母),函数体是<exp>
      |<exp>*<exp>
将后一个<exp>应用于前一个<exp>表示的lambda expression
          |(<exp>)
可以随便加括号 
以下是一些例子:
^x.x
^x.x*x
^x.^y.x*y
(^x.x*x)*(^x.x*x)

*是left associative的,它的优先级比^….…高。输出(稍后会讲到)的lambda expression是full-parenthesized,所以拿不准加不加括号的话可以输入然后输出,对照输出结果检查。在^<var>.<exp>中,<var>的scope是<exp>,如果相同的变量名字母在<exp>中又一次被用于定义函数,后者会在其scope中遮挡外面的变量,比如:
^x.…(^x.x)…
 第3个x绑定到第2个,而非第1个,这和通常语言的lexical scoping是一致的。输出的时候变量会被重新命名,整个表达式中不会出现重名变量。
最后还有一点要求,一个well-formed lambda expression不允许自由变量。也就是说所有变量必须先出现在^<var>中,然后才能在<exp>中。

以上是lambda expression的定义,parse函数照此解析lambda expression,比如:
require 'rambda'
e = parse '^y.(^x.(^y.x*y))*y'

parse返回的lambda expression对象的inspect方法用于输出:
puts e.inspect
在实现细节上,parse是一个递归下降解析器,parse调用函数Exp和Func,分别解析<exp>*<exp>和^<var>.<exp>。这里没有专门的lexer,各函数使用Ruby内置的正则表达式tokenize。

现在说parse返回的对象的结构,也就是程序员们将要用程序操作的结构。在这个库中,一个lambda expression由三种对象(三个类)Var,Func和App表示。其中Var代表一个变量,Func代表一个函数定义,App代表application。
App类最简单,与客户程序员有关的成员只有两个:
class App
  attr_accessor :exp1, :exp2
end

表示将exp2应用于exp1。
Func和Var对象互相指涉:
class Func
  attr_accessor :vars, :body
end

class Var
  attr_accessor :func
end

一个Func对象的vars成员是数组,包含对所有在<exp>部分中出现,绑定到<var>的Var对象的引用(Ruby数组储存对对象的引用而不是对象本身)。而上述的这些Var对象,它们的func成员都指向这个Func对象(Ruby变量储存对象的引用而不是对象本身)。
比如parse '^x.x*x'的结果是一个Func对象,它的vars包含两个元素,因为x在函数体里出现了两次,分别由两个Var对象代表。Func对象的body成员指向一个App对象,它的exp1、exp2就分别指向前述的两个Var对象。这两个Var对象的func都指向前面的Func对象。
好了,有了以上说明,我们可以动手玩弄lambda calculus了。有一个重要经验与大家分享,就是在转换lambda expression的时候,把所有的对象都看成是不可变的。为了简单明了,App,Func,Var三个类并没有限制修改,但是请牢记:一但构建出对象,设定了其成员变量的值以后,就不要再改变了。如果需要变化,请重新建立一个对象,设定其成员变量为“改变”后的值,并以此新对象代替旧对象。

最后是使用本库的一些例子。特别说明:本库在活动期间赠送一个DeepSubstitute1函数,此函数以某种顺序递归做beta reduction直到不能再做。以下的例子包含了一些lambda 地下党的黑话,如果你正在学习lambda calculus,你自然会明白。
a0 = '(^s.^z.z)'
S =  '(^w.^y.^x.y*(w*y*x))'

a1 = DeepSubstitute1(parse("#{S}*#{a0}")).inspect

结果为(^a.(^b.(a*b)))
a2 = DeepSubstitute1(parse("#{S}*#{a1}")).inspect
结果为(^c.(^d.(c*(c*d))))
a3 = DeepSubstitute1(parse("#{S}*#{a2}")).inspect
结果为(^e.(^f.(e*(e*(e*f)))))
a4 = DeepSubstitute1(parse("#{S}*#{a3}")).inspect
结果为(^g.(^h.(g*(g*(g*(g*h))))))
看到了吧,数字0,1,2,3,4就这么生下来了。
T = '(^x.^y.x)' 代表逻辑真
F = '(^x.^y.y)' 代表逻辑假
Neg = "(^x.x*#{F}*#{T})" 代表逻辑取反
Z = "(^x.x*#{F}*#{Neg}*#{F})" 测试是否为0
Phi = "(^p.^z.z*(#{S}*(p*#{T}))*(p*#{T}))"
P = "(^n.n*#{Phi}*(^z.z*#{a0}*#{a0})*#{F})"
返回比给定整数小一数字
Y = "(^y.(^x.y*(x*x))*(^x.y*(x*x)))" 传说中的Y combinator来了
R = "(^r.^n.#{Z}*n*#{a0}*(n*#{S}*(r*(#{P}*n))))"
puts DeepSubstitute1(parse("#{Y}*#{R}*#{a3}")).inspect
计算3!,结果是(^k.(^l.(k*(k*(k*(k*(k*(k*l)))))))),代表6。
rambda.rb在这里:https://files.cnblogs.com/yushih/rambda.zip
posted on 2008-06-20 18:59  yushih  阅读(1001)  评论(1编辑  收藏  举报