2014-11-07 2 views
2

Трудно сформулировать хорошие вопросы как новичок. Пожалуйста, задайте этот вопрос дружественным =)Могу ли я опустить монаду IO на эту чистую функцию?

Пытаясь написать мою первую «настоящую» программу 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 

Это работает, но монада IO в parseConfiguration не является необходимым, и должен уйти. Но я не могу просто бросить его, конечно, и я еще не нашел способ изменить parseConfiguration на что-то чистое, сохранив при этом близость loadConfiguration.

Каков правильный способ написать это? Если на это ответили в документации, извините, но я ее не нашел. Я думаю, что чтение документации по хакерству - это умение, которое растет так же медленно, как и остальные мои навыки Хаскелла. =)

P.S .: Комментарии к другим ошибкам стиля, конечно, очень приветствуются!

+1

Это выглядит как вы можете просто абстрагироваться от внутреннего типа монады? 'parseConfiguration :: Monad m => ByteString -> ExceptT String m Configuration' – Lee

+3

В идеале вы должны иметь' parseConfiguration :: (Monad m, MonadError String m) => m Configuration'. – bheklilr

+0

@bheklilr И если по какой-либо причине вы не используете 'mtl', можно использовать' mapExceptT (return. RunIdentity) :: Monad n => Except ea -> ExceptT ena' – danidiaz

ответ

5

Если вы уже используете mtl, тогда решение, данное bheklilr в своем комментарии, является хорошим. Сделайте parseConfiguration работы на любой монаде, которая реализует MonadError.

Если по каким-либо причинам вы не используете mtl, но только transformers, то вы need'll функцию с типом, как Monad n => Except e a -> ExceptT e n a, что «тали» Except в ExceptT над некоторой монады.

Мы можем построить эту функцию, используя mapExceptT :: (m (Either e a) -> n (Either e' b)) -> ExceptT e m a -> ExceptT e' n b, функцию, которая может изменить базовую монаду трансформатора ExceptT.

Except действительно ExceptT Identity, так что мы хотим, чтобы разворачивать Identity и возвращает значение в новой монады:

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

Вы также можете определить это следующим образом:

hoistExcept :: Monad n => Except e a -> ExceptT e n a 
hoistExcept = ExceptT . return . runIdentity . runExceptT 
+0

Я использую mtl, и мне пришлось использовать '{- # LANGUAGE RankNTypes # -}' и '{- # LANGUAGE FlexibleConstraints # -}', но это работает. Благодаря! – Jens

Смежные вопросы