捕获避免替换函数——Lambda演算
Capture-avoiding substitution function -- Lambda calculus
我正在尝试编写一个在 Lambda 演算中执行避免捕获替换的函数。代码编译但没有吐出正确答案。我已经编写了我希望代码执行的操作,我的理解是否正确?
例如,对于此输入我应该得到以下输出(numeral 0
是教堂数字 0)
*Main> substitute "b" (numeral 0) example -- \a. \x. ((\y. a) x) b
\c. \a. (\a. c) a (\f. \x. x)
-- The incorrect result I actually got
\c. \c. (\f. \x. x) (x (\b. a))
注意 \y
由于替换 (\y.a)[N/b]
而重命名为 \a
错了。)
import Data.Char
import Data.List
type Var = String
data Term =
Variable Var
| Lambda Var Term
| Apply Term Term
-- deriving Show
instance Show Term where
show = pretty
example :: Term -- \a. \x. ((\y. a) x) b
example = Lambda "a"
(Lambda "x" (Apply (Apply (Lambda "y" (Variable "a"))
(Variable "x"))
(Variable "b")))
pretty :: Term -> String
pretty = f 0
where
f i (Variable x) = x
f i (Lambda x m) = if i /= 0 then "(" ++ s ++ ")" else s
where s = "\" ++ x ++ ". " ++ f 0 m
f i (Apply n m) = if i == 2 then "(" ++ s ++ ")" else s
where s = f 1 n ++ " " ++ f 2 m
substitute :: Var -> Term -> Term -> Term
substitute x n (Variable y)
--if y = x, then leave n alone
| y == x = n
-- otherwise change to y
| otherwise = Variable y
substitute x n (Lambda y m)
--(\y.M)[N/x] = \y.M if y = x
| y == x = Lambda y m
--otherwise \z.(M[z/y][N/x]), where `z` is a fresh variable name
--generated by the `fresh` function, `z` must not be used in M or N,
--and `z` cannot be equal `x`. The `used` function checks if a
--variable name has been used in `Lambda y m`
| otherwise = Lambda newZ newM
where newZ = fresh(used(Lambda y m))
newM = substitute x n m
substitute x n (Apply m2 m1) = Apply newM2 newM1
where newM1 = substitute x n m2
newM2 = substitute x n m1
used :: Term -> [Var]
used (Variable n) = [n]
used (Lambda n t) = merge [n] (used t)
used (Apply t1 t2) = merge (used t1) (used t2)
variables :: [Var]
variables = [l:[] | l <- ['a'..'z']] ++
[l:show x | x <- [1..], l <- ['a'..'z']]
filterFreshVariables :: [Var] -> [Var] -> [Var]
filterFreshVariables lst = filter ( `notElem` lst)
fresh :: [Var] -> Var
fresh lst = head (filterFreshVariables lst variables)
recursiveNumeral :: Int -> Term
recursiveNumeral i
| i == 0 = Variable "x"
| i > 0 = Apply(Variable "f")(recursiveNumeral(i-1))
numeral :: Int -> Term
numeral i = Lambda "f" (Lambda "x" (recursiveNumeral i))
merge :: Ord a => [a] -> [a] -> [a]
merge (x : xs) (y : ys)
| x < y = x : merge xs (y : ys)
| otherwise = y : merge (x : xs) ys
merge xs [] = xs
merge [] ys = ys
substitute x n (Lambda y m)
中的这部分不正确:
- 评论说“
z
不得在 M
或 N
中使用”,但没有什么可以阻止的。 newZ
可能是 n
中的变量,这会导致捕获有问题
- 替换
z/y
还没有完成
| otherwise = Lambda newZ newM
where newZ = fresh(used(Lambda y m))
newM = substitute x n m
修复:
- "
z
不得用于 M
或 N
":
newZ = fresh(used m `merge` used n)
- "
M[z/y][N/x]
":
newM = substitute x n (substitute y (Variable newZ) m)
放在一起:
| otherwise = Lambda newZ newM
where
newZ = fresh(used m `merge` used n)
newM = substitute x n (substitute y (Variable newZ) m)
请注意,按照上述方式刷新 所有 绑定会使理解结果和调试替换变得困难。其实y
只有在y
在n
的时候才需要刷新。否则你可以保留y
,添加这个子句:
| y `notElem` used n = Lambda y (substitute x n m)
另一个想法是修改 fresh
以选择一个与旧名称相似的名称,例如,通过附加数字直到一个不冲突。
还有一个bug我漏掉了:newZ
也不应该等于x
(原来被替换的变量)。
-- substitute [a -> \f. \x. x] in (\g. g), should be (\g. g)
ghci> substitute "a" (numeral 0) (Lambda "g" (Variable "g"))
\a. \g. \x. x
解决这个问题的两种方法:
将 x
添加到变量集以排除 newZ
来自:
newZ = fresh ([x] `merge` used m `merge` used n)
如果你仔细想想,这个bug只有在x
不在m
时才会出现,在这种情况下没有什么可以替代的,所以另一种方法是再添加一个分支跳过工作:
| x `notElem` used m = Lambda y m
放在一起:
substitute x n (Lambda y m)
--(\y.M)[N/x] = \y.M if y = x
| y == x = Lambda y m
| x `notElem` used m = Lambda y m
| y `notElem` used n = Lambda y (substitute x n m)
| otherwise = Lambda newZ newM
where newZ = fresh(used m `merge` used n)
newM = substitute x n (substitute y (Variable newZ) m)
输出
ghci> example
\a. \x. (\y. a) x b
ghci> numeral 0
\f. \x. x
ghci> substitute "b" (numeral 0) example
\a. \c. (\y. a) c (\f. \x. x)
注意:我没有尝试证明此代码正确(reader 的练习:定义 "correct"),可能仍然存在我遗漏的错误。肯定有一些关于 lambda 演算的课程,其中包含所有细节和陷阱,但我懒得看。
我正在尝试编写一个在 Lambda 演算中执行避免捕获替换的函数。代码编译但没有吐出正确答案。我已经编写了我希望代码执行的操作,我的理解是否正确?
例如,对于此输入我应该得到以下输出(numeral 0
是教堂数字 0)
*Main> substitute "b" (numeral 0) example -- \a. \x. ((\y. a) x) b \c. \a. (\a. c) a (\f. \x. x) -- The incorrect result I actually got \c. \c. (\f. \x. x) (x (\b. a))
注意 \y
由于替换 (\y.a)[N/b]
而重命名为 \a
错了。)
import Data.Char
import Data.List
type Var = String
data Term =
Variable Var
| Lambda Var Term
| Apply Term Term
-- deriving Show
instance Show Term where
show = pretty
example :: Term -- \a. \x. ((\y. a) x) b
example = Lambda "a"
(Lambda "x" (Apply (Apply (Lambda "y" (Variable "a"))
(Variable "x"))
(Variable "b")))
pretty :: Term -> String
pretty = f 0
where
f i (Variable x) = x
f i (Lambda x m) = if i /= 0 then "(" ++ s ++ ")" else s
where s = "\" ++ x ++ ". " ++ f 0 m
f i (Apply n m) = if i == 2 then "(" ++ s ++ ")" else s
where s = f 1 n ++ " " ++ f 2 m
substitute :: Var -> Term -> Term -> Term
substitute x n (Variable y)
--if y = x, then leave n alone
| y == x = n
-- otherwise change to y
| otherwise = Variable y
substitute x n (Lambda y m)
--(\y.M)[N/x] = \y.M if y = x
| y == x = Lambda y m
--otherwise \z.(M[z/y][N/x]), where `z` is a fresh variable name
--generated by the `fresh` function, `z` must not be used in M or N,
--and `z` cannot be equal `x`. The `used` function checks if a
--variable name has been used in `Lambda y m`
| otherwise = Lambda newZ newM
where newZ = fresh(used(Lambda y m))
newM = substitute x n m
substitute x n (Apply m2 m1) = Apply newM2 newM1
where newM1 = substitute x n m2
newM2 = substitute x n m1
used :: Term -> [Var]
used (Variable n) = [n]
used (Lambda n t) = merge [n] (used t)
used (Apply t1 t2) = merge (used t1) (used t2)
variables :: [Var]
variables = [l:[] | l <- ['a'..'z']] ++
[l:show x | x <- [1..], l <- ['a'..'z']]
filterFreshVariables :: [Var] -> [Var] -> [Var]
filterFreshVariables lst = filter ( `notElem` lst)
fresh :: [Var] -> Var
fresh lst = head (filterFreshVariables lst variables)
recursiveNumeral :: Int -> Term
recursiveNumeral i
| i == 0 = Variable "x"
| i > 0 = Apply(Variable "f")(recursiveNumeral(i-1))
numeral :: Int -> Term
numeral i = Lambda "f" (Lambda "x" (recursiveNumeral i))
merge :: Ord a => [a] -> [a] -> [a]
merge (x : xs) (y : ys)
| x < y = x : merge xs (y : ys)
| otherwise = y : merge (x : xs) ys
merge xs [] = xs
merge [] ys = ys
substitute x n (Lambda y m)
中的这部分不正确:
- 评论说“
z
不得在M
或N
中使用”,但没有什么可以阻止的。newZ
可能是n
中的变量,这会导致捕获有问题 - 替换
z/y
还没有完成
| otherwise = Lambda newZ newM
where newZ = fresh(used(Lambda y m))
newM = substitute x n m
修复:
- "
z
不得用于M
或N
":
newZ = fresh(used m `merge` used n)
- "
M[z/y][N/x]
":
newM = substitute x n (substitute y (Variable newZ) m)
放在一起:
| otherwise = Lambda newZ newM
where
newZ = fresh(used m `merge` used n)
newM = substitute x n (substitute y (Variable newZ) m)
请注意,按照上述方式刷新 所有 绑定会使理解结果和调试替换变得困难。其实y
只有在y
在n
的时候才需要刷新。否则你可以保留y
,添加这个子句:
| y `notElem` used n = Lambda y (substitute x n m)
另一个想法是修改 fresh
以选择一个与旧名称相似的名称,例如,通过附加数字直到一个不冲突。
还有一个bug我漏掉了:newZ
也不应该等于x
(原来被替换的变量)。
-- substitute [a -> \f. \x. x] in (\g. g), should be (\g. g)
ghci> substitute "a" (numeral 0) (Lambda "g" (Variable "g"))
\a. \g. \x. x
解决这个问题的两种方法:
将
x
添加到变量集以排除newZ
来自:newZ = fresh ([x] `merge` used m `merge` used n)
如果你仔细想想,这个bug只有在
x
不在m
时才会出现,在这种情况下没有什么可以替代的,所以另一种方法是再添加一个分支跳过工作:| x `notElem` used m = Lambda y m
放在一起:
substitute x n (Lambda y m)
--(\y.M)[N/x] = \y.M if y = x
| y == x = Lambda y m
| x `notElem` used m = Lambda y m
| y `notElem` used n = Lambda y (substitute x n m)
| otherwise = Lambda newZ newM
where newZ = fresh(used m `merge` used n)
newM = substitute x n (substitute y (Variable newZ) m)
输出
ghci> example
\a. \x. (\y. a) x b
ghci> numeral 0
\f. \x. x
ghci> substitute "b" (numeral 0) example
\a. \c. (\y. a) c (\f. \x. x)
注意:我没有尝试证明此代码正确(reader 的练习:定义 "correct"),可能仍然存在我遗漏的错误。肯定有一些关于 lambda 演算的课程,其中包含所有细节和陷阱,但我懒得看。