问题描述
我今天早些时候在写一些Haskell。提出了类似
的方法{-# LANGUAGE GeneralizednewtypeDeriving #-}
newtype Foo a = Foo { ParsecT String () (Writer DefinitelyAMonoid) a }
deriving (Functor,applicative,Monad,MonadWriter DefinitelyAMonoid)
这没有编译。 GHC告诉我:“ (MonadWriter DefinitelyAMonoid (ParsecT String () (Writer DefinitelyAMonoid)))
的任何实例都不来自数据类型声明的'deriving'子句。”因此,我决定看看其他一些MTL类是否也可以工作。 Reader
和MonadReader
,以及Writer
和MonadWriter
。因此,我由不和谐用户将 turned 定向到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都不提供MonadWriter
的{{1}}实例?这样的解析器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
实例,但我认为有充分的理由不-阻止犯下我认为您可能犯的错误。