mrkkrp/megaparsec

Type class for parser monads with debugging

Lev135 opened this issue · 4 comments

As it is mentioned in docs

it's not possible to lift this function into some monad transformers without introducing surprising behavior (e.g. unexpected state backtracking) or adding otherwise redundant constraints (e.g. Show instance for state), so this helper is only available for ParsecT monad, not any instance of MonadParsec in general

However, there are cases, when it would be very useful. For example for debugging parser with some state:

xCounter :: (MonadParsec Void String m, MonadState Depth m) => m Int

I propose to add special class (something like MonadParsecDbg) and instances for it with "redudant constraints", i. e. Show instance for state and writer's log type. This also gives us very useful state logging in debug mode (if we consider composed transformer m as a parser with state, it seems very natural for me, that it is printed along with parser state).

Some example of how it can be used

Suggested solution:

class Monad m => MonadParsecDbg m where
  dbg :: Show a => String -> m a -> m a

instance (VisualStream s, ShowErrorComponent e) => MonadParsecDbg (ParsecT e s m) where
  dbg = Text.Megaparsec.Debug.dbg

instance (Show s, MonadParsecDbg m) => MonadParsecDbg (StateT s m) where
  dbg str sma = StateT $ \s ->
    dbg str $ runStateT sma s

instance (Show w, Monoid w, MonadParsecDbg m) => MonadParsecDbg (WriterT w m) where
  dbg str wma = WriterT $ dbg str $ runWriterT wma

instance (MonadParsecDbg m) => MonadParsecDbg (ReaderT e m) where
  dbg str rma = ReaderT $ \e -> dbg str $ runReaderT rma e

Maybe it would be better to add MonadParsec constraint for class and e, s parameters. Here I've simplified it for to make it more readable.

I think it is a great idea. Would you like to turn it into a PR?

@mrkkrp Yes I'd like to implement it. As I understand, it should be located in Text.Megaparsec.Debug. However, I'm not sure about some points:

  • Can we just move dbg in class without preserving it's global declaration? As far as I can see, nothing should be broken by this change, but maybe I don't see some problem.
  • Also, not everything is clear with dbg' function. Should it also be included in MonadParsecDbg class? In this case it will print parser state/log (with show constraints on StateT and WriterT arguments). I think it's reasonable, because we can parse some unshowable value, but have normal showable state. However, it's may be more consistent not to add Show constraint in dbg' realization and ignore state/log just like returned value. In this case we'll need another class for dbg' function (with no constraints).
  • Text.Megaparsec.Debug is a good place for this new type class IMO.
  • dbg becomes a method of the new type class, I do not see any problems with this. Both the new type class and its methods are exposed in the API.
  • I think for the first iteration dbg' could be just defined in terms of dbg without becoming a method of MonadPasecDbg. One more type class in overkill, let's have the Show constraints on state/accumulator and have dbg' with its Blind wrapper handle unshowable things.

Wonderful. I was having trouble figuring out why 9.2.2 dbg didn't work with my (StateT ...) parsers. 9.3 dbg just works and is super useful, thanks!