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