为什么 ParsecT 没有 MonadWriter 实例?

Why no MonadWriter instance for ParsecT?

我今天早些时候写了一些 Haskell。想出了一些类似

的东西
{-# LANGUAGE GeneralizedNewtypeDeriving #-}

newtype Foo a = Foo { ParsecT String () (Writer DefinitelyAMonoid) a }
    deriving (Functor, Applicative, Monad, MonadWriter DefinitelyAMonoid)

这没有编译。 “数据类型声明的 'deriving' 子句没有 (MonadWriter DefinitelyAMonoid (ParsecT String () (Writer DefinitelyAMonoid))) 的实例”,GHC 告诉我。所以我决定看看其他一些 MTL 类 是否可行,他们确实可行。 ReaderMonadReader,以及 WriterMonadWriter。所以我 转向 被一个 Discord 用户引导到 Hackage,问题很明显:

MonadState  s m => MonadState  s (ParsecT s' u m)
MonadReader r m => MonadReader r (ParsecT s  u m)
MonadError  e m => MonadError  e (ParsecT s  u m)

没有 MonadWriter 实例可以通过 ParsecT 变压器!对我来说,这似乎只是一个疏忽,在 Github 上打开一个问题之前,我几乎没有意识到这可能是故意的。所以我看了一下 Megaparsec:

(Stream s, MonadState st m) => MonadState st (ParsecT e s m)
(Stream s, MonadReader r m) => MonadReader r (ParsecT e s m)
(Stream s, MonadError e' m) => MonadError e' (ParsecT e s m)

同样,没有 MonadWriter

为什么 Parsec 和 Megaparsec 都不为 ParsecT 提供 MonadWriter 实例?像这样的解析器 monad 是否有什么特别之处使它们无法与编写者相处融洽?是否有类似 this 的情况发生?

Parsec 是一个回溯单子。虽然 MonadWriter 您提出的实例并非不可能,但将 Writer 放在回溯单子下面是一件奇怪的事情。这是因为 monad 堆栈的内层提供了外层构建的“基础”效果——也就是说,无论 ParsecT u (Writer w) 做什么,它都必须只 tell。因此,如果它 tell 是一个分支中的值,后来被回溯出去,则不可能“解开”该值,因此 Writer 的结果将更像是解析 算法 而不是 Parsec 呈现的数据流抽象的踪迹。它不是没用,但它很奇怪,如果 WriterT 在外面而 Parsec 在里面,你会有更自然的语义。

同样的论点适用于 State。 Parsec 公开了它自己的用户状态功能(u 参数)和一个继承底层 monad 状态功能的 MonadState 实例。后者带有与 MonadWriter 相同的注意事项——状态遵循算法的轨迹,而不是数据流。我不知道为什么包含此实例而 Writer 实例不包含;他们都以同样的方式棘手。

-- I'm presuming the user might want a separate, non-backtracking
-- state aside from the Parsec user state.
instance (MonadState s m) => MonadState s (ParsecT s' u m) where
    get = lift get
    put = lift . put

Parsec 的用户状态,另一方面,遵循数据流,而不是计算,它与将 StateT 放在外部具有相同的效果。 Parsec 提供自己的状态工具而不是仅仅要求我们使用转换器,这似乎总是很奇怪——但是,现在想想,我怀疑这只是为了避免在你需要的时候把 lift 到处都是碰巧使用状态。

总而言之,他们可以提供您要求的 MonadWriter 实例,但我认为有充分的理由不提供 - 以阻止犯我认为您可能正在犯的错误。