10个版本 (4 个破坏性更新)
使用旧的Rust 2015
0.5.0 | 2018年10月13日 |
---|---|
0.4.0 | 2018年10月1日 |
0.3.2 | 2018年8月21日 |
0.2.3 | 2018年7月29日 |
0.1.0 | 2018年7月22日 |
在 数据结构 中排名第 572
71KB
1.5K SLoC
Moniker
Moniker使跟踪编程语言实现中嵌套作用域内的变量变得简单。
而不是手动实现名称处理代码(这通常很繁琐且容易出错),Moniker提供了一些类型和特质,用于在语言的抽象语法树中声明性描述名称绑定。从这个角度来看,我们可以自动推导出相应的名称处理代码!
动机
值得注意的是,"作用域"这一概念在编程中经常出现
描述 | Rust 示例 |
---|---|
case match 子句 | matchexpr{x=> /*`x` 在此处绑定*/ } |
let 绑定 | letx= ...; /*`x` 在此处绑定*/ |
递归函数 | fn foo() { /*`foo` 在此处绑定*/ } |
函数参数 | fn foo(x:T) { /*`x` 在此处绑定*/ } |
递归类型 | enum List {Nil, /*`List` 在此处绑定*/ |
类型参数 | struct Point<T> { /*`T` 在此处绑定*/ } |
例如,让我们来看一个Rust函数的愚蠢示例
type Count = u32;
fn silly<T>((count, data): (Count, T)) -> T {
match count {
0 => data,
count => silly((count - 1, data)),
}
}
这里实际上有很多名称绑定!让我们将这些绑定者与其对应的绑定者联系起来
// ?
// |
// v
type Count = u32;
// |
// '----------------------.
// .-------------------------|------------------.
// | .--------------------|----*------. |
// | | | | | |
// | | v v v |
fn silly<T>((count, data): (Count, T)) -> T { // |
// | | |
// .--' | |
// | *--------------. |
// v | | |
match count { // | | |
// .------' | |
// | | |
// v | |
0 => data, // | |
// .--------------. | |
// | | | |
// | v v |
count => silly((count - 1, data)), // |
// ^ |
// | |
// '-----------------------------'
}
}
跟踪这些变量之间的关系可能会很痛苦,在实现评估器和类型检查器时尤其如此。Moniker旨在以最小痛苦的方式支持所有这些绑定结构!
示例
以下是使用Moniker描述一个非常小的函数式语言的示例,包括变量、匿名函数、应用和let绑定
#[macro_use]
extern crate moniker;
use moniker::{Embed, Binder, Rec, Scope, Var};
use std::rc::Rc;
/// Expressions
///
/// ```text
/// e ::= x variables
/// | \x => e anonymous functions
/// | e₁ e₂ function application
/// ````
#[derive(Debug, Clone, BoundTerm)]
// ^
// |
// The derived `BoundTerm` implementation
// does all of the heavy-lifting!
pub enum Expr {
/// Variables
Var(Var<String>),
/// Anonymous functions (ie. lambda expressions)
Lam(Scope<Binder<String>, RcExpr>),
/// Function applications
App(RcExpr, RcExpr),
}
pub type RcExpr = Rc<Expr>;
我们现在可以通过以下方式构建lambda表达式
let f = FreeVar::fresh(Some("f".to_string()));
let x = FreeVar::fresh(Some("x".to_string()));
// \f => \x => f x
Rc::new(Expr::Lam(Scope::new(
Binder(f.clone()),
Rc::new(Expr::Lam(Scope::new(
Binder(x.clone()),
Rc::new(Expr::App(
Rc::new(Expr::Var(Var::Free(f.clone()))),
Rc::new(Expr::Var(Var::Free(x.clone()))),
)),
)))
)))
更完整的示例
更完整的示例,包括评估器和类型检查器,可以在 moniker/examples
目录下找到。
示例名称 | 描述 |
---|---|
lc |
无类型lambda演算 |
lc_let |
具有嵌套let绑定的无类型lambda演算 |
lc_letrec |
具有相互递归绑定的无类型lambda演算 |
lc_multi |
带多绑定的无类型λ演算 |
stlc |
带有字面量的简单类型λ演算 |
stlc_data |
带有记录、变体、字面量和模式匹配的简单类型λ演算 |
stlc_data_isorec |
带有记录、变体、字面量、模式匹配和等价递归类型的简单类型λ演算 |
使用Moniker的项目
Moniker目前正在以下Rust项目中使用
- Pikelet: 一种依赖类型系统编程语言
特性和数据类型概述
我们将数据类型分为项和模式
项
项是实现了BoundTerm
特质的枚举。
Var<N>
: 一个自由或绑定的变量Scope<P: BoundPattern<N>, T: BoundTerm<N>>
: 使用模式P
绑定项T
Ignore<T>
: 在比较α等价时忽略T
还提供了元组、字符串、数字、切片、向量和指针的实现,以方便使用。
模式
模式是实现了 BoundPattern
特质的枚举。
Binder<N>
: 在项内捕获自由变量,但在α等价比较时被忽略Ignore<T>
: 在比较α等价时忽略T
Embed<T: BoundTerm<N>>
: 将项T
嵌入模式中Nest<P: BoundPattern<N>>
: 多重嵌套绑定模式Rec<P: BoundPattern<N>>
: 递归绑定模式自身
还提供了元组、字符串、数字、切片、向量和指针的实现,以方便使用。
路线图
Moniker目前足够用于初始语言原型,但我们希望做更多工作,使其成为一个更完整的名称绑定和作用域处理工具包。
- 使用局部无名称表示的初始实现
- 实现基本类型组合
-
嵌入
-
忽略
-
嵌套
-
递归
-
作用域
-
- 自动推导特质
-
BoundTerm
-
BoundPattern
-
Subst
-
- 允许使用除了
String
之外的标识符类型进行派生 - 实现命名空间变量和绑定器
- 性能优化
- 缓存项的最大深度
- 缓存项的自由变量
- 执行多次打开/关闭操作
- 使用访问者
- 实现基本类型组合
- 探索实现其他名称绑定方案
- 使用唯一索引命名
- 作用域图
- ...?
参考
以下是构建Moniker时有所帮助的一些有趣参考文献和先验知识。请注意,阅读和理解这些内容并非使用库的必要条件,但如果你希望贡献,它们可能有用!
论文
博客文章
其他库
API主要受Haskell的Unbound和Unbound-Generics库的启发,有一些不同之处。我们做的最主要的变化是将Unbound的单个Alpha
类型类替换为两个独立的特质(BoundTerm
和BoundPattern
)。我们发现这更好地捕捉了库的语义,并且大大减少了意外误用的可能性。
存在许多不同的语言的自绑定库
- Unbound:使用一组表达性类型组合子指定您的数据类型的绑定结构,然后由Unbound处理其余部分!自动推导alpha等价性、自由变量计算、避免捕获的替换等。
- Unbound-Generics:Unbound的独立重新实现,但使用GHC.Generics而不是Replib。
- Cαml:将所谓的“绑定规范”转换为OCaml编译单元的工具。
- alphaLib:一个OCaml库,帮助处理抽象语法树中的绑定结构。
- abbot:为SML生成抽象绑定树
- rabbot:将SML的Abbot移植到Rust
- DBLib:在Coq中处理de Bruijn索引的设施
- Bound:Haskell的DeBruijn索引
- Metalib:宾夕法尼亚本地无名字元理论库
- LibLN:具有有限量化局部无名字符串表示法
贡献
我们非常希望鼓励新贡献者帮助我们!请到我们的Gitter频道聊天 - 如果你对该项目有任何问题,或者只是想打个招呼!
致谢
这项工作部分得益于YesLogic的慷慨支持。
依赖关系
~2.5MB
~56K SLoC