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.
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')