我发现的解析器组合器上的材料涵盖了通过组合构建复杂的解析器,但我想知道是否有任何好的方法来通过调整库的组合解析器来定义解析器而不完全复制原始库的逻辑。
例如,这是Real world Haskell
中定义的简化CSV解析器import Text.ParserCombinators.Parsec
csvFile = endBy line eol
line = sepBy cell (char ',')
cell = many (noneOf ",\n")
eol = char '\n'
假设在一个库中定义了csvFile
,另一个库可以使用自定义版本的cell
解析器创建自己的CSV解析器,而无需重写line
和{{1}解析器也是?可以重写源库以使其成为可能吗?这对于CSV解析器来说非常简单,但我对广泛适用的解决方案感兴趣。
答案 0 :(得分:9)
通常,您需要抽象出要替换的组件的签名。例如,在CSV示例中,我们需要扩展csvFile
的类型,以允许我们插入自定义cell
。
line cell = sepBy cell (char ',')
csvFile cell = endBy (line cell) eol
显然这很快变得笨拙。我们可以将所需的所有扩展点打包到字典中,然后将其传递给
data LanguageDefinition =
LanguageDefinition { cell :: Parser Cell
, ...
}
然后我们通过LanguageDefinition
data Parsers = Parsers { line :: Parser Line, csvFile :: Parser [Line], ... }
mkParsers :: LanguageDefinition -> Parsers
这正是Parsec的通用令牌解析模块所采用的方法:请参阅Text.Parsec.Token和Text.Parsec.Language。
可以采用更通用的方法,将越来越多的东西抽象到字典中。实际上,这成为面向对象或OCaml模块的组织代码的方法,并且非常有效。
民间表达问题表明在引入更多功能和引入更多变体之间存在紧张关系。在这种情况下,您需要一个新的变体,因此您需要修复功能(并将其全部列在字典中)。这将开辟一条引入新变种的道路。