-
-
Save spl/ba42e143d3c56af755b9 to your computer and use it in GitHub Desktop.
$ ghci | |
GHCi, version 7.10.2: http://www.haskell.org/ghc/ :? for help | |
-- This is a similar to a function found in some library somewhere. | |
Prelude> let lookupM k v = maybe (fail $ show k ++ " not found in " ++ show v) return $ lookup k v | |
Prelude> :t lookupM | |
lookupM | |
:: (Eq a1, Monad m, Show a, Show a1) => a1 -> [(a1, a)] -> m a | |
-- What happens when you use Either as the monad? | |
Prelude> lookupM "a" [] :: Either String Int | |
*** Exception: "a" not found in [] | |
-- Oops! I expected: Left "\"a\" not found in []" | |
-- But maybe we should use ErrorT, as suggested by @JohnLato. | |
Prelude> :m +Control.Monad.Trans.Error | |
<interactive>:1:1: Warning: | |
Module ‘Control.Monad.Trans.Error’ is deprecated: | |
Use Control.Monad.Trans.Except instead | |
Prelude Control.Monad.Trans.Error> :t runErrorT $ lookupM "a" [] | |
runErrorT $ lookupM "a" [] | |
:: (Monad m, Show a, Error e) => m (Either e a) | |
Prelude Control.Monad.Trans.Error> runErrorT $ lookupM "a" [] :: IO (Either String ()) | |
Left "\"a\" not found in []" | |
-- That works, but ErrorT is deprecated in favor of ExceptT. | |
-- Let's try ExceptT. | |
Prelude Control.Monad.Trans.Error> :m -Control.Monad.Trans.Error | |
Prelude> :m +Control.Monad.Trans.Except | |
Prelude Control.Monad.Trans.Except> :t runExceptT $ lookupM "a" [] | |
runExceptT $ lookupM "a" [] :: (Monad m, Show a) => m (Either e a) | |
Prelude Control.Monad.Trans.Except> runExceptT $ lookupM "a" [] :: IO (Either String ()) | |
*** Exception: user error ("a" not found in []) | |
-- Hmm, that's not what we want. |
I've updated the example to tell the story better and demonstrate alternatives (ErrorT
and ExceptT
).
ErrorT
can do that because of the Error e
constraint on the Monad (ErrorT e m)
instance. That has its own problems though, since not everything is an instance of Error
. So ExceptT
and Either
have a more general instance for Monad
, without the constraint. That means they can't do anything in fail
, since e
is polymorphic, and not known to be String
.
I basically treat fail
like it's deprecated. I don't use it, since too many instances can't implement it so there's the risk of bottoms everywhere. I'm also suspicious of any function with just a Monad
constraint, especially if it's a parsing or lookup like thing. I can't wait for something like the MonadFail proposal to go through.
@rampion Right. That's certainly simpler and straight to the point. But I would argue that my example is slightly more subtle. The actual issue I encountered involved even more code and was even more subtle.