How do I rewrite the following code, so that it:
case ... of ...-- parseSQL :: String -> Either ParseError SQL
-- evalSQL :: SQL -> IO (Either EvalError Table)
-- prettyPrintTable :: Table -> IO ()
-- ParseError, EvalError and Table are instances of Show
evalAndPrint :: String -> IO ()
evalAndPrint x =
case parseSQL x of
(Left parseErr) ->
print parseErr
(Right sql) -> do
result <- evalSQL sql
case result of
(Left err) ->
print err
(Right table) -> do
prettyPrintTable table
putStrLn $ "(" ++ show (length table) ++ " lines)\n"
For the moment, let's assume you've generalized your parseSQL and evalSQL functions to have these types (later we'll see how to turn your specialized implementations into generalized ones even if you don't have access to their source):
parseSQL :: MonadError ParseError m => String -> m SQL
evalSQL :: (MonadError EvalError m, MonadIO m) => SQL -> m Table
Then we can write:
-- if we were really doing this the mtl way, we'd introduce a new
-- type class for changing error types instead of specializing to
-- ExceptT, but that's another answer
evalAndThrow :: String -> ExceptT String IO ()
evalAndThrow s = do
sql <- withExceptT show (parseSQL s)
table <- withExceptT show (evalSQL sql)
liftIO $ prettyPrintTable table
liftIO . putStrLn $ "(" ++ show (length table) ++ " lines)\n"
The top-level function can then be something like
evalAndPrint s = do
v <- runExceptT (evalAndThrow s)
case v of
Left err -> putStrLn err
Right _ -> return ()
Here are some tricks for converting your existing functions into the mtl-style polymorphic versions. Either you can change their source directly, or you can make adapters using combinators like these:
-- this is a generally useful combinator
liftEither :: MonadError e m => Either e a -> m a
liftEither = either throwError return
-- this is a combinator specific to your question
liftIOEither :: (MonadError e m, MonadIO m) => IO (Either e a) -> m a
liftIOEither = join . liftIO . liftM liftEither
And of course there is also ExceptT :: IO (Either e a) -> ExceptT e IO a; ExceptT . evalSQL is not quite as polymorphic as liftIOEither . evalSQL, but since we're using it at the ExceptT type it may not matter in this case.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With