如何从这段代码中删除`case of`?

时间:2015-03-04 15:03:26

标签: haskell

如何重写以下代码:

  1. 使用较少的字符
  2. 最多包含一个case ... of ...
  3. -- 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"
    

1 个答案:

答案 0 :(得分:3)

目前,让我们假设您已将parseSQLevalSQL函数概括为具有这些类型(稍后我们将会看到如何转换您的专业实现即使您无法访问其来源,也可以使用广义的内容):

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类型上使用它,因此在这种情况下可能无关紧要。