-
-
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. |
A simpler example:
λ fail "hello" :: Either String Int
*** Exception: hello
And even more surprising:
λ (fail :: String -> Either String Int) "hello"
*** Exception: hello
λ let failE = fail :: String -> Either String Int
λ :t failE
failE :: String -> Either String Int
λ failE "hello"
*** Exception: hello
@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.
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.
https://plus.google.com/+SeanLeather/posts/DDbhBu538dm