我可以把这个纯函数上的IO monad丢弃吗?

时间:2014-11-07 13:33:32

标签: haskell monad-transformers

很难将好问题标题定为新手。请使这个问题搜索友好=)

尝试编写我的第一个“真正的”Haskell程序(即不仅仅是Project Euler的东西),我正在尝试使用漂亮的错误消息来读取和解析我的配置文件。到目前为止,我有这个:

import Prelude hiding (readFile)
import System.FilePath (FilePath)
import System.Directory (doesFileExist)
import Data.Aeson
import Control.Monad.Except
import Data.ByteString.Lazy (ByteString, readFile)

-- Type definitions without real educational value here

loadConfiguration :: FilePath ->  ExceptT String IO Configuration
loadConfiguration path = do
    fileContent     <- readConfigurationFile "C:\\Temp\\config.json"
    configuration   <- parseConfiguration fileContent
    return configuration

readConfigurationFile :: FilePath -> ExceptT String IO ByteString
readConfigurationFile path = do
    fileExists <- liftIO $ doesFileExist path
    if fileExists then do
        fileContent <- liftIO $ readFile path        
        return fileContent
    else
        throwError $ "Configuration file not found at " ++ path ++ "."

parseConfiguration :: ByteString -> ExceptT String IO Configuration
parseConfiguration raw = do
    let result = eitherDecode raw :: Either String Configuration
    case result of 
        Left message        -> throwError $ "Error parsing configuration file: " ++ message
        Right configuration -> return configuration

这样可行,但parseConfiguration中的IO monad不是必需的,应该消失。但是我当然不能放弃它,而且我还没有找到办法将parseConfiguration更改为纯粹的东西,同时保持loadConfiguration的漂亮。

写这个的正确方法是什么?如果在文档中回答这个问题,我很抱歉,但我没有找到它。我认为阅读hackage文档是一项技能,其增长速度与我的其余Haskell技能一样慢。 =)

P.S。:对其他风格错误的评论当然非常受欢迎!

1 个答案:

答案 0 :(得分:5)

如果你已经在使用mtl,那么bheklilr在他的评论中给出的解决方案是一个很好的解决方案。让parseConfiguration适用于任何实现MonadError的monad。

如果由于某种原因你没有使用mtl,而只使用transformers,那么你需要一个类似Monad n => Except e a -> ExceptT e n a类型的功能,&#34;提升&# 34; <{1}} Except超过一些monad。

我们可以使用ExceptT来构建这个函数,这个函数可以改变mapExceptT :: (m (Either e a) -> n (Either e' b)) -> ExceptT e m a -> ExceptT e' n b变换器的基本monad。

ExceptT真的是Except,所以我们想要的是打开ExceptT Identity并返回新monad中的值:

Identity

您也可以这样定义:

hoistExcept :: Monad n => Except e a -> ExceptT e n a
hoistExcept = mapExceptT (return . runIdentity)