231
社区成员
Implement the substitution function N[v/x] : subst (N: lambda, x: string, v: value) : lambda
Think about how substitution works on arbitrary terms, i.e. N[M/x] where M could contain free variables.
Implement Church numberals and arithmetic functions using lambda calculus
实现替换函数subst(N:lambda, x:string, v:lambda)
。
对于Var(a)
类型的lambda
表达式,如果a == x
,那么使用v
替换Var(a)
;
对于App(m,n)
类型的lambda
表达式,那么分别在m
和n
中替换v
;
对于Fun(f,body)
类型的lambda
表达式,需要根据f
和v
中的自由变量进行分类讨论:
在body
中,对于变量f
的引用均指向受控变量f
(bounded variable f);
在v
中,有可能存在对自由变量f
(free variable f)的引用;
两类引用指向的变量名称虽然相同,但是实际含义并不相同。比如(λx.λy.yx)M
,应用β规约,应当使用M
替换λy.yx
中的变量x
。注意λy.yx
仍然是一个函数,函数体yx
中引用了受控变量y
。如果M
中包含自由变量y
,直接使用M
替换x
得到λy.yM
会得到错误的结果。一种极端情况是M = y
,替换的结果是λy.yy
,显然不正确。
回到Fun(f,body)
。对于这种情况,需要在body
中存在与函数的受控变量f
相同名称的自由变量时,为受控变量f
重新命名,保证新名称不和body
中的自由变量重复。上例中,如果在使用M
替换λy.yx
中的变量x
前,将y
替换为M
中没有引用的变量名z
,那么替换的结果为λz.zM
,当M = y
时,替换的结果为λz.zy
,是正确的结果。
下面贴下代码。代码中将产生冲突的函数受控变量重命名为@i
。顺带给出了lambda表达式open form的求值函数eval:
module Lambda = {
type rec t =
| Var (string)
| App (t, t)
| Fun (string, t)
let rec toString = (t : t) :string => {
switch t {
| Var(x) => x
| App(m, n) => "(" ++ toString(m) ++ toString(n) ++ ")"
| Fun(f, arg) => "(λ" ++ f ++ "." ++ toString(arg)++")"
}
}
let rec equal = (s:t ,t: t) : bool => {
switch (s,t) {
| (Var(a), Var(b)) => a == b
| (App(m, n), App(p, q)) => equal(m, p) && equal(n, q)
| (Fun(f, a), Fun(g, b)) => f == g && equal(a, b)
| _ => false
}
}
let rec getFreeVariables = (t: t): list<string> => {
switch t {
| Var(x) => list{x}
| App(m, n) => Belt.List.concatMany([getFreeVariables(m), getFreeVariables(n)])
| Fun(f, arg) => getFreeVariables(arg)->Belt.List.keep((x)=> x != f)
}
}
let nameOrder = ref(0)
// get a new name "@i", where i comes from a mutable int
let getNewname = () : string => {
nameOrder := nameOrder.contents + 1
"@" ++ Js.Int.toString(nameOrder.contents)
}
// When va contains unbounded variable with the same name of bounded varaibles in body,
// conflicts will occur.
// Solution: assign new name to those bounded varaiables in body which share the same names of unbounded variables.
let rec subst = (x: string, va:t, body: t) : t => {
switch body {
| Var(a) if a == x => va
| Var(_) => body
| App(m, n) => App(subst(x, va, m), subst(x, va, n))
| Fun(f, arg) => if Belt.List.has(getFreeVariables(va),f,String.equal) {
let newSym = getNewname()
let newArg = subst(f,Var(newSym),arg)
if f == x {
Fun(newSym,subst(newSym,va,newArg))
}
else {
Fun(newSym, subst(x, va, newArg))
}
}
else {
if f == x {
body
}
else {
Fun(f, subst(x, va, arg))
}
}
}
}
let rec eval = (t: t) => {
switch t {
| Var(_) => t
| Fun(x, body) => Fun(x, eval(body))
| App(f, arg) => {
switch eval(f) {
| Fun(x, body) => {
let va = eval(arg)
eval(subst(x, va, body))
}
| k => App(k, eval(arg))
}
}
}
}
}
使用lambda实现Church number和算数运算。
下面是实现代码。PPT中提到的乘法运算的递归实现无法直接用上节的eval进行求值。涉及到Y组合子和Ω组合子的表达式,使用eval求值无法终止。
let omega = {
open Lambda
let smallOmega = Fun("x",App(Var("x"),Var("x")))
App(smallOmega, smallOmega)
}
let ycomb = {
open Lambda
Fun("f",App(Fun("x",App(Var("f"),App(Var("x"),Var("x")))),Fun("x",App(Var("f"),App(Var("x"),Var("x"))))))
}
Js.log(Lambda.toString(ycomb))
let succ = {
open Lambda
Fun("n",Fun("f",Fun("x",App(Var("f"),App(App(Var("n"),Var("f")),Var("x"))))))
}
let zero = {
open Lambda
Fun("f",Fun("x",Var("x")))
}
let one = {
open Lambda
Fun("f",Fun("x",App(Var("f"),Var("x"))))
}
let mul = {
open Lambda
Fun("n",Fun("m",Fun("f",App(Var("n"),App(Var("m"),Var("f"))))))
}
let exp = {
open Lambda
Fun("n",Fun("m",App(Var("m"),Var("n"))))
}
let if_then_else = {
open Lambda
Fun("t",Fun("x",Fun("y",App(App(Var("t"),Var("x")),Var("y")))))
}
let trueC = {
open Lambda
Fun("x",Fun("y",Var("x")))
}
let falseC = {
open Lambda
Fun("x",Fun("y",Var("y")))
}
let add = {
open Lambda
Fun("n",Fun("m",Fun("f",Fun("x",App(App(Var("n"),Var("f")),App(App(Var("m"),Var("f")),Var("x")))))))
}
let iszero = {
open Lambda
Fun("n",App(App(Var("n"),Fun("z",falseC)),trueC))
}
let pair = {
open Lambda
Fun("x",Fun("y",Fun("z",App(App(Var("z"),Var("x")),Var("y")))))
}
let fst = {
open Lambda
Fun("p",App(Var("p"),trueC))
}
let snd = {
open Lambda
Fun("p",App(Var("p"),falseC))
}
let pred = {
open Lambda
Fun("n",App(
fst,
App(
App(
Var("n"),
Fun("p",App(
App(pair,App(
snd,
Var("p"))),
App(succ,App(
snd,
Var("p")))))),
App(
App(
pair,
zero),
zero))))
}
let mulR = {
open Lambda
let f = {
Fun("f",Fun("n",Fun("m",App(App(App(if_then_else,App(iszero,Var("n"))),zero),App(App(add,Var("m")),App(App(Var("f"),App(pred,Var("n"))),Var("m")))))))
}
App(ycomb,f)
}
let toChurchNum = (n :int) => {
let rec helper = (n : int, churchNum: Lambda.t) : Lambda.t =>
if n < 0 {assert false}
else {
switch n {
| 0 => churchNum
| _ => helper(n-1, App(succ,churchNum))
}
}
helper(n,zero)
}
let numbers = {
list{
toChurchNum(0),
toChurchNum(1),
toChurchNum(2),
toChurchNum(3),
toChurchNum(4),
toChurchNum(5)
}
}
let churchNumToInt = (t : Lambda.t) :int => {
open Lambda
let f = Var("f")
let x = Var("x")
let va = eval(App(App(t,f),x))
let rec decomposeChurch = (va' :t, f' :t, x' : t) => {
switch va' {
| Var(_) if va' == x' => 0
| App(g, y) if g == f' => decomposeChurch(y, f', x') + 1
| _ => assert false
}
}
decomposeChurch(va,f,x)
}
let mul2by2 = {
open Lambda
App(App(mulR,toChurchNum(2)),toChurchNum(2))
}
下面是测试:
Js.log(Lambda.toString(omega))
Js.log(Lambda.toString(ycomb))
List.iter((x)=>(Js.log(Lambda.toString(Lambda.eval(x)))), numbers)
List.iter((x)=>(Js.log(Lambda.toString(Lambda.eval(App(pred,x))))), numbers)
Js.log(churchNumToInt(App(pred,toChurchNum(15))))
输出:
((λx.(xx))(λx.(xx)))
(λf.((λx.(f(xx)))(λx.(f(xx)))))
(λf.(λx.x))
(λf.(λx.(fx)))
(λf.(λx.(f(fx))))
(λf.(λx.(f(f(fx)))))
(λf.(λx.(f(f(f(fx))))))
(λf.(λx.(f(f(f(f(fx)))))))
(λf.(λx.x))
(λf.(λx.x))
(λf.(λ@5.(f@5)))
(λf.(λ@14.(f(f@14))))
(λf.(λ@26.(f(f(f@26)))))
(λf.(λ@41.(f(f(f(f@41))))))
14
赞