我该如何重写以下代码,以便它:
使用更少的字符
最多包含一个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"
最佳答案
目前,让我们假设您已将parseSQL
和evalSQL
函数泛化为具有这些类型(稍后,即使您无法访问其源代码,我们也会看到如何将您的专用实现转换为泛化的实现) :
parseSQL :: MonadError ParseError m => String -> m SQL
evalSQL :: (MonadError EvalError m, MonadIO m) => SQL -> m Table
然后我们可以写:
-- 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"
顶层函数可以是类似
evalAndPrint s = do
v <- runExceptT (evalAndThrow s)
case v of
Left err -> putStrLn err
Right _ -> return ()
以下是一些将现有函数转换为mtl样式的多态版本的技巧。您可以直接更改其来源,也可以使用如下组合器制造适配器:
-- 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
当然还有
ExceptT :: IO (Either e a) -> ExceptT e IO a
; ExceptT . evalSQL
的多态性不如liftIOEither . evalSQL
,但是由于我们在ExceptT
类型中使用它,因此在这种情况下可能无关紧要。关于haskell - 如何从此代码中删除“case of”?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/28857745/