debruijn 教程
本教程将命名 lambda 项转换为 De Bruijn 形式,利用这种形式比较项并在无需重命名的情况下归约它们,验证不可信的无名输入,并将结果转换回带名字的形式以便显示。
快速入门
moon add Luna-Flow/type_theory@0.2.0
import {
"Luna-Flow/type_theory/core",
"Luna-Flow/type_theory/syntax",
"Luna-Flow/type_theory/rewrite",
"Luna-Flow/type_theory/debruijn",
}
变为 :该变量指向向外一层的绑定子。
test "quick start: the K combinator without names" {
let x = @core.Name::new("x")
let y = @core.Name::new("y")
let k : @syntax.Term[Int] = Bind(x, Bind(y, Variable(x)))
assert_eq(@debruijn.from_named(k), Bind(Bind(Bound(1))))
}
日常任务
在约束名字的意义下比较项
α-等价的命名项具有相同的 De Bruijn 形式,因此转换后的 == 可判定 α-等价:
test "alpha-equivalence by conversion" {
let a : @syntax.Term[Int] = Bind(@core.Name::new("p"), Variable(@core.Name::new("p")))
let b : @syntax.Term[Int] = Bind(@core.Name::new("q"), Variable(@core.Name::new("q")))
assert_eq(@debruijn.from_named(a), @debruijn.from_named(b))
}
@syntax.alpha_equal 无需构建转换后的项即可给出相同的答案;当你需要保留无名形式做进一步处理时,转换才物有所值。
无需重命名的归约
在无名项上的 β-归约通过移位索引而不是重命名绑定子来完成。经典的捕获示例 不需要新鲜名字:
test "beta without capture problems" {
let y = @core.Name::new("y")
let term : @debruijn.DbTerm[Int] = Apply(Bind(Bind(Bound(1))), [Free(y)])
assert_eq(
@debruijn.normalize(term, 10),
NormalForm(term=Bind(Free(y)), steps=1),
)
}
自由的 y 保持自由,因为它是一个名字;内层绑定子不需要新名字,因为它根本没有名字。
验证不可信的输入
手工构建或从外部读取的项可能包含超出所有绑定子的索引。请在归约之前检查它们:
test "reject a dangling index" {
let bad : @debruijn.DbTerm[Int] = Bind(Apply(Bound(0), [Bound(2)]))
match @debruijn.validate(bad) {
Err(UnboundIndex(index~, depth~)) => {
assert_eq(index, 2)
assert_eq(depth, 1)
}
_ => fail("expected an unbound index")
}
}
转换回带名字的形式以便显示
to_named 将绑定子命名为 x、x_1、…,并避开项中的自由名字:
test "readable names come back" {
let x = @core.Name::new("x")
let term : @debruijn.DbTerm[Int] = Bind(Bind(Apply(Bound(1), [Free(x)])))
match @debruijn.to_named(term) {
Ok(Bind(outer, Bind(inner, _))) => {
inspect(outer.text(), content="x_1")
inspect(inner.text(), content="x_2")
}
_ => fail("well scoped")
}
}
绑定子跳过了 x,因为 x 在项中是自由的。
手工打开绑定子
instantiate(body, arg) 是 的结果。在实现你自己的归约器或求值器时使用它:
test "open a binder" {
// body of λ. (λ. 1 0): the outer variable applied to the inner one
let body : @debruijn.DbTerm[Int] = Bind(Apply(Bound(1), [Bound(0)]))
assert_eq(
@debruijn.instantiate(body, Value(9)),
Ok(Bind(Apply(Value(9), [Bound(0)]))),
)
}
进阶
用无名归约器检验命名归约器
由于转换与 β-归约可交换,命名归约器与无名归约器必须在 α-等价意义下一致。对于你编写的任何归约器,这都是一个很好的性质测试:
fn named_beta(t : @syntax.Term[Int]) -> @syntax.Term[Int]? {
match t {
Apply(Bind(x, body), [arg]) =>
Some(@substitution.Substitution::singleton(x, arg).apply(body))
_ => None
}
}
test "named and nameless beta agree" {
let x = @core.Name::new("x")
let y = @core.Name::new("y")
let named : @syntax.Term[Int] = Apply(Bind(x, Bind(y, Apply(Variable(x), [Variable(y)]))), [
Variable(y),
])
match (named_beta(named), @debruijn.reduce_once(@debruijn.from_named(named))) {
(Some(n), Reduced(after~, ..)) => assert_eq(@debruijn.from_named(n), after)
_ => fail("both reduce")
}
}
(此示例还导入了 Luna-Flow/type_theory/substitution。)
快速范式化
normalize 从根部反复执行一次“搜索并收缩”的步骤,简单且可追踪,但对于很长的归约很慢。对于大型无类型项,请使用 utlc/nbe,它作用于相同的 DbTerm 并返回相同的范式(在应用脊的形状意义下)。
常见陷阱
- 从外向内计数索引。
Bound(0)是最内层的绑定子。 是Bind(Bind(Bound(1))),而不是Bound(0)。 - 归约未经验证的项。
reduce_once和substitute_bound不会检测未绑定的索引。对于不是用from_named构建的输入,请先调用validate。 - 期望绑定子名字得以保留。
to_named(from_named(t))与tα-等价,但绑定子名字是重新生成的。 - 应用脊。
Apply(f, [a, b])和Apply(Apply(f, [a]), [b])表示同一个应用,但它们是不同的值;用==比较之前请先将它们规整为同一种形状。
后续步骤
- debruijn API:每个函数和错误情形。
- debruijn 设计:移位、实例化以及正确性引理。
- utlc/nbe 教程:
DbTerm上的求值范式化。