我正在寻找一种简单的方法来组合ParsecT代码的两个部分,它们具有相同的流和monad,但具有不同的用户状态和结果。 基本上这样的函数会很好:
withUserState :: u -> ParsecT s u m a -> ParsecT s v m a
问题是,用户状态在某些情况下确实很有用, 但我在不同的时间需要不同的状态 并且不想让状态类型更大。 我是否必须以某种方式修改状态以实现此目的, 还是已经有了我目前无法找到的功能?
编辑:
我认为替代方案可能是
changeUserState :: (u -> v) -> ParsecT s u m a -> ParsecT s v m a
答案 0 :(得分:9)
Parsec不允许您直接开箱即用,但您可以使用Parsec的公共API实现此目的,如下所示:
{-# LANGUAGE ScopedTypeVariables #-}
import Text.Parsec
changeState
:: forall m s u v a . (Functor m, Monad m)
=> (u -> v)
-> (v -> u)
-> ParsecT s u m a
-> ParsecT s v m a
changeState forward backward = mkPT . transform . runParsecT
where
mapState :: forall u v . (u -> v) -> State s u -> State s v
mapState f st = st { stateUser = f (stateUser st) }
mapReply :: forall u v . (u -> v) -> Reply s u a -> Reply s v a
mapReply f (Ok a st err) = Ok a (mapState f st) err
mapReply _ (Error e) = Error e
fmap3 = fmap . fmap . fmap
transform
:: (State s u -> m (Consumed (m (Reply s u a))))
-> (State s v -> m (Consumed (m (Reply s v a))))
transform p st = fmap3 (mapReply forward) (p (mapState backward st))
请注意,它需要u
和v
之间的前向和后向转换。原因是首先需要将环境状态转换为本地状态,运行内部解析器,然后转换回来。
ScopedTypeVariables
和本地类型签名只是为了清楚起见 - 如果您愿意,可随意删除它们。
答案 1 :(得分:2)
由于>>=
运算符的类型为
ParsecT s u m a -> (a -> ParsecT s u m b) -> ParsecT s u m b
和(<*>)
为
ParsecT s u m (a -> b) -> ParsecT s u m a -> ParsecT s u m b
s
变量是普遍量化的,但必须与这两个术语相匹配。如果没有>>=
或<*>
,则不能使用任何应用或monadic函数。这意味着您绝对无法将任何具有不同状态的解析器组合在一起。最好的方法就是
data PotentialStates = State1 ...
| State2 ...
| State3 ...
然后只与他们合作。