8

我正在寻找一种简单的方法来组合 ParsecT 代码的两个部分,它们具有相同的流和 monad,但用户状态和结果不同。基本上像这样的功能会很好:

withUserState :: u -> ParsecT s u m a -> ParsecT s v m a

问题是,用户状态在某些情况下确实很有帮助,但我在不同的时间需要不同的状态,并且不想让状态类型变得更大。我是否必须以某种方式修改 State 才能实现这一点,还是已经有一个我目前找不到的功能?

编辑:

我认为另一种选择是

changeUserState :: (u -> v) -> ParsecT s u m a -> ParsecT s v m a
4

2 回答 2

10

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和本地类型签名只是为了清楚起见——如果你愿意,可以随意删除它们。

于 2013-07-31T12:12:16.203 回答
3

您不能这样做,因为>>=运算符具有类型

 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变量是普遍量化的,但必须与这两个术语匹配。没有>>=<*>您不能使用任何应用或单子函数。这意味着您绝对无法将任何具有不同状态的解析器组合在一起。做到这一点的最好方法就是

data PotentialStates = State1 ...
                     | State2 ...
                     | State3 ...

然后就用这些来代替。

于 2013-07-31T11:24:22.770 回答