最近的工作经常要写 parser,也就有了改进的目标,这几天对 Jaskell Core 做了一个大版本升级,主要是将内置组合子作为扩展,一次打包封装为 typeclasses 。
例如,以前这样定义的 parser:
"Brackets" should "test brackets pairs" in {
val state = State("[hello]");
val parser = Between(
ch('['),
ch(']'),
many(Ne(']'))
)
val re = parser(state).map(_.mkString)
re should be (Success("hello"))
}
现在可以写为更面向对象的风格:
"Combinator" should "test typeclasses style" in {
import jaskell.parsec.Combinator.BuiltIn
val state = State("[hello]");
val parser = nch(']').many.between(ch('['), ch(']'))
val re = parser(state).map(_.mkString)
re should be (Success("hello"))
}
这对于一些需要多层组合的代码,是个有效的优化,例如我们经常要在一些解释器里写类似 `attempt((prefix), attempt(parser))` 这样的代码,现在可以写成 `do(prefix.attempt, parser.attempt)` 。
在 scala 中,这样的支持不会导致内置组合子和自定义组合子的“不平等”,因为它并不是侵入式的在 Parsec trait 中加入新方法,而是通过 implicit class 扩展实现:
object Combinator {
//...
implicit class BuiltIn[E, T](p: Parsec[E, T]) {
def attempt: Parsec[E, T] = Attempt(p)
def ahead: Parsec[E, T] = Ahead(p)
def or(other: Parsec[E, T]): Parsec[E, T] = Attempt(p) <|> other
def many: Parsec[E, Seq[T]] = Many(p)
def many1: Parsec[E, Seq[T]] = Many1(p)
def manyTill(end: Parsec[E, _]): Parsec[E, Seq[T]] = ManyTill(p, end)
def skip: Parsec[E, Unit] = Skip(p)
def skip1: Parsec[E, Unit] = Skip1(p)
def sepBy(by: Parsec[E, _]): Parsec[E, Seq[T]] = SepBy(p, by)
def sepBy1(by: Parsec[E, _]): Parsec[E, Seq[T]] = SepBy1(p, by)
def find: Parsec[E, T] = Find(p)
def between(open: Parsec[E, _], close: Parsec[E, _]): Parsec[E, T] = Between(open, close, p)
}
}
在需要使用这些扩展的代码中,`import jaskell.parsec.Combinator.BuiltIn ` ,即可。同理,我们也可以根据需要添加新的扩展。
从效果上看,这个扩展使代码更加“面向对象”,但是在实现上,这恰恰是函数式编程的 typeclass 风格。当然,Objective C 和 C Sharp,也早就有类似的语法支持。在 Ruby 中,这种对类型“重定义”的功能,可以通过注入实现。
这个功能在 Jaskell Core 0.7.1 中已经可用。同样,在 Jaskell Dotty 中,我们通过 Scala 3 的extension 语法,也提供了这个能力:
extension [E, T](parsec: Parsec[E, T]) {
def attempt: Parsec[E, T] = Attempt(parsec)
def ahead: Parsec[E, T] = Ahead(parsec)
def or(other: Parsec[E, T]): Parsec[E, T] = Attempt(parsec) <|> other
def many: Parsec[E, Seq[T]] = Many(parsec)
def many1: Parsec[E, Seq[T]] = Many1(parsec)
def manyTill(end: Parsec[E, _]): Parsec[E, Seq[T]] = ManyTill(parsec, end)
def skip: Parsec[E, Unit] = Skip(parsec)
def skip1: Parsec[E, Unit] = Skip1(parsec)
def sepBy(by: Parsec[E, _]): Parsec[E, Seq[T]] = SepBy(parsec, by)
def sepBy1(by: Parsec[E, _]): Parsec[E, Seq[T]] = SepBy1(parsec, by)
def find: Parsec[E, T] = Find(parsec)
def between(open: Parsec[E, _], close: Parsec[E, _]): Parsec[E, T] = Between(open, close, parsec)
}
目前 Jaskell Dotty 已经升级至 0.4.0,支持 scala 3.0.2 和内置组合子扩展。