Search code examples
haskellnestedparseceither

Haskell: Handling resulting Either from computations


I have revisited Haskell lateley and constructed a toy programming language parser/interpreter. Using Parsec for lexing and parsing and a separate interpreter. I'm running in to some issues with feeding the result from the parser to my interpreter and handle the potential error from both the interpreter and parser. I end up with something like this:

main = do
  fname <- getArgs
  input <- readFile (head fname)
  case lparse (head fname) input of
    Left msg -> putStrLn $ show msg
    Right p -> case intrp p of
      Left msg -> putStrLn $ show msg
      Right r -> putStrLn $ show r

This dosn't look pretty at all. My problem is that lparse returns Either ParseError [(String, Stmt)] and itrp returns the type Either ItrpError Stmt so I'm having a real hard time feeding the Right result from the parser to the interpreter and at the same time bail and print the possible ParseError or IntrpError.

The closest to what i want is something like this

main = do
  fname <- getArgs
  input <- readFile (head fname)
  let prog = lparse (head fname) input
  (putStrLn . show) (intrp <$> prog)

But this will not surprisingly yield a nested Either and not print pretty either.

So are there any nice Haskell ideomatic way of doing this threading results from one computation to another and handling errors (Lefts) in a nice way without nesting cases?

Edit

adding types of lparse and itrp

lparse :: Text.Parsec.Pos.SourceName -> String -> Either Text.Parsec.Error.ParseError [([Char], Stmt)]

intrp :: [([Char], Stmt)] -> Either IntrpError Stmt

Solution

  • While not perfect, I'd create a helper function for embedding any Showable error from Either into MonadError:

    {-# LANGUAGE FlexibleContexts #-}
    import Control.Monad.Except
    
    strErr :: (MonadError String m, Show e) => Either e a -> m a
    strErr = either (throwError . show) return
    

    Then if you have a computation that can fail with errors, like

    someFn :: ExceptT String IO ()
    someFn = strErr (Left 42)
    

    you can run it (printing errors to stdout) as

    main :: IO ()
    main = runExceptT someFn >>= either putStrLn return
    

    In your case it'd be something like

    main = either putStrLn return <=< runExceptT $ do
      fname <- liftIO getArgs
      input <- liftIO $ readFile (head fname)
      prog <- strErr $ lparse (head fname) input
      r <- strErr $ interp prog
      print r