如何重写以下代码:
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"
答案 0 :(得分:3)
目前,让我们假设您已将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
类型上使用它,因此在这种情况下可能无关紧要。