Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to remove `case of` from this code?

Tags:

haskell

How do I rewrite the following code, so that it:

  1. uses less characters
  2. contains at most one 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"
like image 925
Not an ID Avatar asked Dec 28 '25 18:12

Not an ID


1 Answers

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.

like image 81
Daniel Wagner Avatar answered Jan 01 '26 12:01

Daniel Wagner



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!