Ruskell ( Dwarfartisan/ruskell · GitHub ) 是我在学习 rust 的过程中做的一些积累。首先是 Parsec 的移植,其次是一些函数式编程概念的实现尝试。其详细的文档在 《 Ruskell——Haskell Parsec 的 Rust 移植 - 前路迢迢 - 知乎专栏 》。近期我又对这个库做了一次升级,简化了算子的结构和构造方式。
在过去的设计中,Ruskell Parsec 的结构总是一个实现了Parsec(Parsec 实现了 Monad)的 struct,为了使用简单,内置算子我都实现了 Fn/FnMut/FnOnce 。这样的代价是每个算子的实现代价非常高。尽管一部分算子我基于一个通用的 Parser struct 实现,但是仍然是一个比较重的代价。每一个算子对象,都是一个携带着 abc 形式的 struct ,而 abc 宏是 Arc 包装中的一个闭包的 Box。这样至少需要三层封装。
在重构 goparsec2 的时候(Parsec 的迁移(一) - 前路迢迢 - 知乎专栏) ,我意识到其实 rust 版本也可以处理为一个类型别名和一个有默认实现的 traits 的组合。于是我做了一些尝试。
因为 rust 的语法比 go 苛刻的多,这个过程并非一帆风顺。开始我试图直接将在闭包Fn(State)->Status的类型别名上绑定已实现的 Parsec 。但是同样遇到 size 问题。最终我还是将类型别名定义到了 abc (Arc Box Closure)结构上。
pub type Parser = Arc)->Status>>;
impl Parsec for Parser where T:Clone, R:Clone {
fn parse(&self, state: &mut State) -> Status {
self(state)
}
}
impl Monad for Parser where T:Clone, R:Clone {}
是的,新的解析子类型只需要这么几行,因为不用再写 new 和 Fn/FnMut/FnOnce 实现,比原有的代码量小了太多。而且再定义新的算子的时候也简单的多,只要定义一个fn,返回 Parser 类型就可以了,例如:
pub fn try(p:X)->Parser
where T:Clone, R:Clone, X:Parsec+Clone {
abc!(move |state: &mut State|->Status{
let pos = state.pos();
let res = p.parse(state);
if res.is_err() {
state.seek_to(pos);
}
res
})
}
目前很多算子还没有仔细的审读其代码是否可以进一步优化,但是基本上都简化为一个parser的构造逻辑了。
而其它逻辑,例如 Monad、Parsec、State的定义,几乎都没有受到影响,调用方式也几乎不需要改变,这说明 rust 的语言抽象能力还是非常好的。下例是 ruskell 代码库中的一段测试函数,在新的设计下也是有效的:
#[test]
fn many1_test_1() {
let mut state = VecState::from_iter("abc".chars().into_iter());
let a = eq('b');
let b = eq('b');
let c = eq('c');
let re = many1(either(try(a), try(b)).or(try(c)))(&mut state);
assert!(re.is_err());
}
唯一的问题出在上例中这个“either”算子的定义,它是choice的一个简化版本,只支持两路分支,如果需要多路选择,就要支持链式的 or 操作。但是在 type aliase 上直接定义方法会报错,于是我又定义了一个 Or trait :
pub trait Or {
fn or(&self, Parser)->Parser;
}
pub type Either = Arc)->Status>>;
pub fn either(x:X, y:Y)->Either
where T:Clone, R:Clone, X:Parsec+Clone, Y:Parsec+Clone{
let x = x.clone();
let y = y.clone();
abc!(move |state:&mut State|->Status{
let pos = state.pos();
let val = x.parse(state);
if val.is_ok() {
val
} else {
if pos == state.pos() {
y.parse(state)
} else {
val
}
}
})
}
impl Or for Either {
fn or(&self, p:Parser)->Parser{
let s:Parser = self.clone();
either(s, p)
}
}
另外就是发现 rust 有 try! 宏以后,可以愉悦的模仿 do 环境了,比如现在 between 可以这样写:
pub fn between (open:Open, close:Close, parsec:X) ->Parser where T:Clone, P:Clone, B:Clone, E:Clone, Open:Monad+Clone, X:Parsec+Clone, Close:Parsec+Clone { let open = open.clone(); let parsec = parsec.clone(); let close = close.clone(); abc!(move |state: &mut State|->Status
{ try!(open.parse(state)); let re = parsec.parse(state); try!(close.parse(state)); re }) }
忽视那几个clone以后,其实关键代码就是:
abc!(move |state: &mut State|->Status{
try!(open.parse(state));
let re = parsec.parse(state);
try!(close.parse(state));
re
})
这么几行,而且清晰的给出了between的逻辑,非常舒服。
还有这段测试代码:
#[test]
fn m_test_2() {
let mut state = VecState::from_iter("abc".chars().into_iter());
let exp:Parser = abc!(|state: &mut State|->Status{
try!(eq('a')(state));
let re = try!(eq('b')(state));
try!(eq('c')(state));
try!(eof()(state));
Ok(re)
});
let re = exp.parse(&mut state);
assert!(re.is_ok());
let data = re.unwrap();
assert_eq!(data, 'b');
}
在目前,经过对几门语言的比较和学习,对各个移植版本的升级也可以告一段落了。