Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

An easy way to change the type of Parsec user state?

Tags:

haskell

parsec

I'm looking for an easy way to combine two parts of ParsecT code that have the same stream and monad, but different user state and outcome. Essentially a function like this would be nice:

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

The thing is, that the user state is really helpful in some cases, but I need different states at different times and don't want to make the states type any bigger. Do I have to modify State somehow to achieve this, or is there already a function for it that I can't find at the moment?

Edit:

I think an alternative would be something like

changeUserState :: (u -> v) -> ParsecT s u m a -> ParsecT s v m a
like image 930
Jakob Runge Avatar asked Jul 31 '13 11:07

Jakob Runge


2 Answers

Parsec doesn't let you do this directly out of the box, but you can achieve this using Parsec's public API as follows:

{-# 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))

Note that it requires both forward and backward conversion between u and v. The reason is that first you need to translate your ambient state to the local state, run your inner parser, an then convert back.

ScopedTypeVariables and local type signatures are there just for clarity — feel free to remove them if you like.

like image 72
Roman Cheplyaka Avatar answered Nov 09 '22 21:11

Roman Cheplyaka


You can't do this as the >>= operator has the type

 ParsecT s u m a -> (a -> ParsecT s u m b) -> ParsecT s u m b

and (<*>) as

 ParsecT s u m (a -> b) -> ParsecT s u m a -> ParsecT s u m b

The s variable is universally quantified, but must match with both terms. Without >>= or <*> you can use no applicative or monadic functions. This means you'd have absolutely no way to combine any parsers with different states. The best way to do this is just

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

and then just work with those instead.

like image 29
Daniel Gratzer Avatar answered Nov 09 '22 22:11

Daniel Gratzer