我对代数数据类型没有太多经验,因为我使用的语言不支持。通常可以使用延续传递样式来获取类似的体验,但是处理 CPS 编码类型不太方便。
考虑到这一点,为什么像 Parsec 这样的库要使用 CPS 呢?
newtype ParsecT s u m a
= ParsecT {unParser :: forall b .
State s u
-> (a -> State s u -> ParseError -> m b) -- consumed ok
-> (ParseError -> m b) -- consumed err
-> (a -> State s u -> ParseError -> m b) -- empty ok
-> (ParseError -> m b) -- empty err
-> m b
}
一个线索是try
解析器,它通过在两种情况下传递空错误的继续来排除消耗错误的情况:
try :: ParsecT s u m a -> ParsecT s u m a
try p =
ParsecT $ \s cok _ eok eerr ->
unParser p s cok eerr eok eerr
-- ^^^^ ^^^^
这是可能的,因为两个 continuation cerr
和 eerr
有相同的类型,仅在位置上略有不同,这让我想起结构类型。虽然我认为你不能在 ADT 中做到这一点,但可能有一种方法可以使用它们来实现相同的行为。除此之外,单个组合子并不能证明依赖于 CPS 的决定具有深远的正当性。那么为什么要做出这个决定呢?
serialise
包使用CPS,但采取其他措施以避免过度依赖优化器(也许避免专业化?)。然而,解析与反序列化并不相同。 - dfeuer