I'm mainly interested in the Either
monad and all it's uilitites from Control.Error
. Reading errors-1.0: Simplified error handling, I got convinced pure errors should be kept apart from IO errors. This means error
, fail
, exitFailure
are functions whose use should be reduced to the IO monad. Pure computations can and will create condition errors, but are deterministic.
Currently, working with folds, I got a situation an element in an array may generate a condition error which makes the whole computation unsatisfiable. For example (using Data.ConfigFile):
type CPError = (CPErrorData, String)
data CPErrorData = ParseError String | ...
type SectionSpec = String
type OptionSpec = String
instance Error CPError
instance Error e => MonadError e (Either e)
get :: MonadError CPError m => ConfigParser -> SectionSpec -> OptionSpec -> m a
dereference :: ConfigParser -> String -> Either CPError String
dereference cp v = foldr replacer v ["executable", "args", "title"]
where
replacer :: String -> Either CPError String -> Either CPError String
replacer string acc = do
res <- acc
value <- get cp "DEFAULT" string
return $ replace ("${" ++ string ++ "}") value res
The situation is: I'm using an acc which has a complex type, just because if a single element isn't found doing the replacement, then the whole value isn't calculable.
My question is: Is this ugly? Is there a better way of doing this? I have some nastier utilities of type EitherT CPError IO String
in the acc
because of some IO checks.
I now understand I was looking into a way to fold a list of composable actions. I came across this question, and learned about the Kleisli operator:
dereferenceValue :: ConfigParser -> String -> Either CPError String
dereferenceValue cp v = do
foldr (>=>) return (fmap replacer ["executable", "args", "title"]) v
where
replacer :: String -> String -> Either CPError String
replacer string res = do
value <- get cp "DEFAULT" string
return $ replace ("${" ++ string ++ "}") value res
Probably, this seems a bit like my question, but it feels cleaner. Particularly because of the signature of replacer
. It doesn't receive a Monad as a second parameter and becomes more useful for other parts of code.
EDIT:
Even easier:
dereferenceValue :: ConfigParser -> String -> Either CPError String
dereferenceValue cp v = do
foldM replacer v ["executable", "args", "title"]
where
replacer :: String -> String -> Either CPError String
replacer res string = do
value <- get cp "DEFAULT" string
return $ replace ("${" ++ string ++ "}") value res
Conclusion: learn to use Hoogle
As you discovered, foldM
works very nicely here. Your replacer
function
replacer :: String -> String -> Either String String
replacer res string = do
value <- get cp "DEFAULT" string
return $ replace ("${" ++ string ++ "}") value res
can be further beautified using Applicative as follows
replacer :: String -> String -> Either String String
replacer res string =
replace ("${" ++ string ++ "}") <$> get cp "DEFAULT" string <*> pure res
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With