前一篇我们讨论了将 Parsec 移植到 Go 语言中遇到的一些问题: Parsec 的迁移(一) - 前路迢迢 - 知乎专栏 。
这里有几个问题,是几乎向任何语言迁移 Parsec 库都会遇到的。例如,如何将算子的定义和Monad化的逻辑正交分解。因为每个算子的逻辑各不相同,但是其Bind/Then行为应该是一致的。另一个是如何平衡错误处理的问题:如果要频繁的处理错误状态,将err作为结果返回比较好。如果要实现do,就要提供一个可以抛出异常的环境。更麻烦的是,有些语言并没有异常处理能力。
Python 版的 Parsec 实现 Dwarfartisan/pyparsec · GitHub 中,我直接放弃了对于错误返回和异常抛出的妥协问题,统一用异常处理。得益于 Python 的 decorate 语法,Monad 封装做的非常的简单:
class Parsec(object):
def __init__(self, parsec):
self.parsec = parsec
def __call__(self, st):
return self.parsec(st)
def bind(self, continuation):
def bind(st):
return continuation(self.parsec(st))
return Parsec(bind)
def then(self, p):
def then(st):
self.parsec(st)
return p(st)
return Parsec(then)
def over(self, p):
def over(st):
re = self.parsec(st)
p(st)
return re
return Parsec(over)
这样,所有的算子只要定义为函数,然后 decorate 一下就好。将原来的函数绑定到修饰后生成的新对象上,作为其 __call__ 方法的行为,bind等monad行为也实现为对应的方法调用。例如 eof :
@Parsec
def eof(state):
re = None
try:
re = state.next()
except ParsecEof:
return None
raise ParsecError(state, "Expect eof but got {0}".format(re))
甚至组合子或生成算子的逻辑也可以,比如eq:
def eq(data):
@Parsec
def call(st):
re = st.next()
if re == data:
return re
else:
raise ParsecError(st, "Expect {0} but got {1}".format(data, re))
return call
和 choices :
def choices(*psc):
if len(psc)<2: raise="" "choices="" need="" more="" args="" than="" one."="" @parsec="" def="" call(st):="" for="" p="" in="" psc[:-1]:="" prev="st.index" try:="" return="" p(st)="" except:="" if="" st.index="" !="prev:" else:="" psc[-1](st)="" call="" <="" code="">得益于 Python 简洁的语法和动态类型,即使不像 goparsec2 一样区分 exception 和 error ,Python 的版本仍然远比其它静态语言的简短易用,自从实现了这个版本,我经常是先用 python 写出解析逻辑,确定测试 case ,再去编写正式项目中的 go 版本逻辑。