purescript-contrib/purescript-parsing

liftMaybe, liftEither, liftExceptT

Closed this issue · 4 comments

“Validation Parsers”

liftMaybe :: forall s m a. Monad m => (Unit -> String) -> Maybe a -> ParserT s m a
liftEither :: forall s m a. Monad m => Either String a -> ParserT s m a
liftExceptT :: forall s m a. Monad m => ExceptT String m a -> ParserT s m a

Similar to
https://pursuit.purescript.org/packages/purescript-transformers/6.0.0/docs/Control.Monad.Error.Class#v:liftMaybe

Similar to
https://pursuit.purescript.org/packages/purescript-protobuf/2.1.2/docs/Protobuf.Library#v:parseMaybe

See also #84

See also https://pursuit.purescript.org/packages/purescript-lazy/6.0.0/docs/Data.Lazy

Or perhaps these should be called maybeParserT, eitherParserT, exceptParserT?

Can these all be written in terms of MonadError? Why should they be specialized to ParserT?

Can these all be written in terms of MonadError?

Yes, in like one line.

Why should they be specialized to ParserT?

Mostly as documentation to encourage users to “parse, don’t validate?”

To clarify, if they are implemented only with MonadError, then their utility is far greater than just ParserT, and it would be worth potentially upstreaming them to transformers if they don't exist.

Why should they be specialized to ParserT?

In Control.Monad.Error.Class we have

-- | Lift a `Maybe` value to a MonadThrow monad.
liftMaybe :: forall m e a. MonadThrow e m => e -> Maybe a -> m a

-- | Lift an `Either` value to a MonadThrow monad.
liftEither :: forall m e a. MonadThrow e m => Either e a -> m a

It's awkward for parsing users to use these functions because the type for e would be ParseError, which has constructor ParseError String Position. We don't want to require users to supply the Position argument of the constructor. We would rather specialize the e type to String and use the current parsing position.

Also it can be expensive to construct Strings, especially on a parsing hot path where we expect a parsing failure to be rare, so in liftMaybe it makes sense to defer the String construction.

So with those adjustments we get

liftMaybe :: forall s m a. Monad m => (Unit -> String) -> Maybe a -> ParserT s m a
liftEither :: forall s m a. Monad m => Either String a -> ParserT s m a

As for liftExceptT (and liftMaybeT?) it”s the same as the above, with the stipulation that if the user needs some special base monad feature for parsing then maybe they also need the special base monad feature for validation. This could be true while parsing DataViews with a base monad Effect, for example.

So that would be

liftMaybeT :: forall s m a. Monad m => (Unit -> String) -> MaybeT m a -> ParserT s m a
liftExceptT :: forall s m a. Monad m => ExceptT String m a -> ParserT s m a

Those two functions don't seem to exist in transformers already but if they did then they would look like

-- | Lift a `MaybeT` value to a MonadThrow monad.
liftMaybeT :: forall m e a. MonadThrow e m => e -> MaybeT m a -> m a

-- | Lift an `ExceptT` value to a MonadThrow monad.
liftExceptT :: forall m e a. MonadThrow e m => ExceptT e m a -> m a