Skip to content

Instantly share code, notes, and snippets.

@spl
Last active October 5, 2015 18:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save spl/ba42e143d3c56af755b9 to your computer and use it in GitHub Desktop.
Save spl/ba42e143d3c56af755b9 to your computer and use it in GitHub Desktop.
Either fail
$ 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.
@spl
Copy link
Author

spl commented Oct 2, 2015

@rampion
Copy link

rampion commented Oct 2, 2015

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

@spl
Copy link
Author

spl commented Oct 5, 2015

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

@spl
Copy link
Author

spl commented Oct 5, 2015

I've updated the example to tell the story better and demonstrate alternatives (ErrorT and ExceptT).

@hesselink
Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment