-
-
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.