haskellmonad-transformersmegaparsec

Switching the monad in Megaparsec


type P = ParsecT Void String (ReaderT UI IO) UI
type Q = ParsecT Void String ((->) UI) (UI -> UI,String)

I know how to do this:

type Q' = ParsecT Void String ((->) UI) (UI,String)
qtoq' p = do 
 x <- ask 
 (a,b) <- p
 return (a x,b)

But how do I do

qprimetop = ... 

This is for a text based battleship game to give me practice with lenses and mtl. I am very confused as it seems theoritically possible, I feel like I just need to use reader, but ParsecT has this:

(a -> State s e -> Hints (Token s) -> m b) -> -- consumed-OK
(ParseError s e -> State s e -> m b) -> -- consumed-error
(a -> State s e -> Hints (Token s) -> m b) -> -- empty-OK
(ParseError s e -> State s e -> m b) -> -- empty-error

So I feel like it must need an inverse of reader as well. Is there any way for me to avoid having to update those fields?

Should I just start with

type Q = ParsecT Void String (ReaderT UI IO) (UI -> UI,String)

I could, but it would require a bit of changing my previous code around.


Solution

  • The general operation of transforming an inner monad through a monad transformer layer is usually called "hoisting", so you want a function:

    hoist :: (Monad m, Monad n)
      => (forall b. m b -> n b)
      -> ParsecT e s m a -> ParsecT e s n a
    

    after which you can write your desired function as:

    q'top :: Q' -> P
    q'top q' = fmap fst (hoist reader q')
    

    (The fmap fst here assumes you want to transform the Q' return value of type (UI, String) to the P return value of type UI by throwing the String away.)

    Writing hoist is a transformer-specific task, and you typically need to dig into the internals. For Megaparsec, the implementation is particularly difficult because of the continuation passing style. There are some hints in Text.Megaparsec.Internal, however, and the following should work:

    hoist h p = ParsecT $ \s cok cerr eok eerr -> do
      Reply s' consumption result <- h (runParsecT p s)
      case (consumption, result) of
        (Consumed, OK hs x)    -> cok x s' hs
        (Consumed, Error e)    -> cerr e s'
        (NotConsumed, OK hs x) -> eok x s' hs
        (NotConsumed, Error e) -> eerr e s'
    

    It runs the original m-parser through h to get a generic Reply in the n monad and then dispatches to the various n-continuations based on that.

    Because h is only called at the Reply type, the type signature can be simplified slightly to remove the forall b, as in the following example code:

    module ParsecIO where
    
    import Control.Monad.Reader
    import Data.Void
    import Text.Megaparsec
    import Text.Megaparsec.Internal
    
    data UI
    
    type P = ParsecT Void String (ReaderT UI IO) UI
    type Q' = ParsecT Void String ((->) UI) (UI,String)
    
    hoist :: (Monad m, Monad n)
      => (m (Reply e s a) -> n (Reply e s a))
      -> ParsecT e s m a -> ParsecT e s n a
    hoist h p = ParsecT $ \s cok cerr eok eerr -> do
      Reply s' consumption result <- h (runParsecT p s)
      case (consumption, result) of
        (Consumed, OK hs x)    -> cok x s' hs
        (Consumed, Error e)    -> cerr e s'
        (NotConsumed, OK hs x) -> eok x s' hs
        (NotConsumed, Error e) -> eerr e s'
    
    q'top :: Q' -> P
    q'top q' = fmap fst (hoist reader q')