I am trying to implement a Parsec Stream
wrapper that will remember the last uncons
'd token in order to provide some look-behind capability. I want the wrapper to work with any Stream
instance. Here's what I have so far:
{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses #-}
module MStream where
import Text.Parsec
import Control.Monad ( liftM )
data MStream s t = MStream (Maybe t) s
instance Stream s m t => Stream (MStream s t) m t where
uncons (MStream _ s) = fmap (\(t, s') -> (t, MStream (Just t) s')) `liftM` uncons s
getPrevToken :: Stream s m t => ParsecT (MStream s t) u m (Maybe t)
getPrevToken = (\(MStream t _) -> t) `liftM` getInput
mstream :: s -> MStream s t
mstream = MStream Nothing
This works, but I don't like having to carry the t
parameter in the MStream
type constructor. Surely it should be sufficient to require only the s
parameter, since t
can be derived from s
as long as there is a witness for Stream s m t
. I've tried using type families and GADTs, but I keep running into obscure errors about ambiguous type variables and unsatisfied functional dependencies.
Is there a way to remove t
from the MStream
type constructor so I don't have to write:
sillyParser :: Stream s m Char => ParsecT (MStream s Char) u m String
sillyParser = do
t <- getPrevToken
maybe (string "first") (\c -> string $ "last" ++ [c]) t
So, I solved this by moving the goalposts (somewhat):
{-# LANGUAGE FlexibleContexts, FlexibleInstances,
MultiParamTypeClasses, FunctionalDependencies #-}
module MStream where
import Text.Parsec
import Control.Monad ( liftM )
class Stream s m t => MStream s m t | s -> t where
getPrevToken :: ParsecT s u m (Maybe t)
data MStreamImpl s t = MStreamImpl (Maybe t) s
instance Stream s m t => MStream (MStreamImpl s t) m t where
getPrevToken = (\(MStreamImpl t _) -> t) `liftM` getInput
instance Stream s m t => Stream (MStreamImpl s t) m t where
uncons (MStreamImpl _ s) = fmap (\(t, s') -> (t, MStreamImpl (Just t) s')) `liftM` uncons s
mstream :: s -> MStreamImpl s t
mstream = MStreamImpl Nothing
sillyParser :: MStream s m Char => ParsecT s u m String
sillyParser = do
t <- getPrevToken
maybe (string "first") (\c -> string $ "last" ++ [c]) t
Instead of trying to remove the type parameter from MStream
, I turned MStream
into a typeclass, with a canonical instance MStreamImpl
. So now, the sillyParser
type signature can be written in a more compact manner simply by replacing the Stream
context with an MStream
context.