syntax 教程
本教程用 Term[T] 构建带绑定子的项、打印它们、询问哪些变量是自由的、在约束变量名不计的意义下比较项,并在不发生捕获的情况下重命名变量。最后你会编写一个适用于任何实现了 BindingSyntax 的 AST 的分析。
快速入门
moon add Luna-Flow/type_theory@0.2.0
import {
"Luna-Flow/type_theory/core",
"Luna-Flow/type_theory/syntax",
}
恒等函数 没有自由变量; 有一个:
test "quick start: free variables" {
let x = @core.Name::new("x")
let y = @core.Name::new("y")
let id : @syntax.Term[Int] = Bind(x, Variable(x))
let k : @syntax.Term[Int] = Bind(x, Variable(y))
assert_eq(@syntax.free_variables(id).length(), 0)
assert_true(@syntax.free_variables(k).contains(y))
}
日常任务
用小型辅助函数构建项
到处写 @core.Name::new 会很啰嗦。一次性定义辅助函数:
fn v(name : String) -> @syntax.Term[Int] {
@syntax.Variable(@core.Name::new(name))
}
fn lam(name : String, body : @syntax.Term[Int]) -> @syntax.Term[Int] {
@syntax.Bind(@core.Name::new(name), body)
}
fn app(f : @syntax.Term[Int], args : Array[@syntax.Term[Int]]) -> @syntax.Term[Int] {
@syntax.Apply(f, args)
}
test "the K combinator" {
let k = lam("x", lam("y", v("x")))
assert_true(k is Bind(_, Bind(_, Variable(_))))
assert_eq(app(k, [v("a")]), @syntax.Apply(k, [v("a")]))
}
打印项
Term 没有自己的文本格式,因为合适的记法取决于它所编码的语言。lambda 记法的打印器是一个简短的递归函数:
fn show(t : @syntax.Term[Int]) -> String {
match t {
Value(n) => n.to_string()
Variable(x) => x.text()
Apply(f, args) => {
let parts = [show(f), ..args.map(show)]
"(" + parts.join(" ") + ")"
}
Bind(x, body) => "λ" + x.text() + ". " + show(body)
}
}
test "print λf. f 1" {
let f = @core.Name::new("f")
let t : @syntax.Term[Int] = Bind(f, Apply(Variable(f), [Value(1)]))
inspect(show(t), content="λf. (f 1)")
}
在约束名字的意义下比较项
== 按字面比较项。alpha_equal 忽略约束变量的名,这几乎总是你想要的:
test "λx. x and λy. y" {
let x = @core.Name::new("x")
let y = @core.Name::new("y")
let a : @syntax.Term[Int] = Bind(x, Variable(x))
let b : @syntax.Term[Int] = Bind(y, Variable(y))
assert_false(a == b)
assert_true(@syntax.alpha_equal(a, b))
}
安全地重命名自由变量
在 中把 x 重命名为 y 不能得到 。rename_free 会先重命名绑定子:
test "renaming avoids capture" {
let x = @core.Name::new("x")
let y = @core.Name::new("y")
let t : @syntax.Term[Int] = Bind(y, Apply(Variable(x), [Variable(y)]))
let r = t.rename_free(@core.Renaming::singleton(x, y))
let y1 = @core.Name::new("y_1")
let expected : @syntax.Term[Int] = Bind(y1, Apply(Variable(y), [Variable(y1)]))
assert_eq(r, expected)
}
自由的 x 变成了 y,而绑定子变成了 y_1,使新的 y 保持自由。
改变常量的类型
map_values 转换常量,不改动绑定结构。这里整数字面量变成浮点字面量:
test "map Int constants to Double" {
let x = @core.Name::new("x")
let t : @syntax.Term[Int] = Bind(x, Apply(Variable(x), [Value(2)]))
let d : @syntax.Term[Double] = t.map_values(n => n.to_double())
assert_true(d is Bind(_, Apply(_, [Value(2.0)])))
}
进阶
为所有感知绑定的 AST 编写分析
针对 BindingSyntax 编写的函数既适用于 Term[T],也适用于任何实现了该 trait 的下游 AST。下面这个函数统计绑定子个数:
fn[N : @syntax.BindingSyntax] count_binders(node : N) -> Int {
match @syntax.BindingSyntax::project(node) {
Opaque | Variable(_) => 0
Apply(head, args) =>
args.fold(init=count_binders(head), (acc, a) => acc + count_binders(a))
Bind(_, body) => 1 + count_binders(body)
}
}
test "count binders generically" {
let x = @core.Name::new("x")
let t : @syntax.Term[Int] = Bind(x, Apply(Bind(x, Variable(x)), [Value(0)]))
assert_eq(count_binders(t), 2)
}
adapter 教程为自定义 AST 实现了 BindingSyntax,之后 count_binders、泛型代换和泛型重写都能应用于它。
手动重命名绑定子
当你从部件构建绑定子时,alpha_rename_bound 会改变其变量的名,并拒绝已在体中使用的名:
test "rename the variable of a binder" {
let x = @core.Name::new("x")
let y = @core.Name::new("y")
let z = @core.Name::new("z")
let body : @syntax.Term[Int] = Apply(Variable(x), [Variable(y)])
assert_true(body.alpha_rename_bound(x, y) is None)
match body.alpha_rename_bound(x, z) {
Some(renamed) => {
let before : @syntax.Term[Int] = Bind(x, body)
let after : @syntax.Term[Int] = Bind(z, renamed)
assert_true(@syntax.alpha_equal(before, after))
}
None => fail("z is unused")
}
}
常见陷阱
==不是 α-等价。 代换和范式化的结果可能含有被重命名的绑定子,例如y_1。请用alpha_equal比较。- 值内部的变量。
Value(T)从不被检查。如果你的常量包含变量,请为你的 AST 实现BindingSyntax,而不是把它包装进Term。 - 对绑定子调用
alpha_rename_bound。 它期望的是体:body.alpha_rename_bound(x, z),而不是Bind(x, body).alpha_rename_bound(...),后者找不到可重命名的自由x。 - 方法风格的 trait 调用。
term.project()已弃用;请写@syntax.BindingSyntax::project(term)。
后续步骤
- syntax API:每个函数及其契约。
- syntax 设计:α-等价与避免捕获的重命名背后的定义和证明。
- substitution 教程:用项替换变量。