3

I have a function that handles errors via Either:

funErrViaEither :: a -> Either SomeException b

I want to use this function in another function that should be more flexible and return MonadThrow m:

funErrViaThrow :: MonadThrow m => a -> m b
funErrViaThrow x =
    if x = someCondition
        then funErrViaEither
        else throwM (SomeException MyCustomException)

This does not compile; the type checker complains that the return type of funErrViaEither does not match the expected type m b. I don't understand why - Either has an instance of MonadThrow with SomeException as the type of Left.

Where do I err? What would be the correct way to convert an error signalled via Either into one signalled via MonadThrow?

Will Ness
  • 70,110
  • 9
  • 98
  • 181
Ulrich Schuster
  • 1,670
  • 15
  • 24
  • 2
    You can't do this in general. You're asking to convert a function which returns an `Either SomeException b` to one that can generically return *any* `m b` where `m` is an instance of `MonadThrow`. Your question is a bit like asking if you can convert an `Int` value into one of type `(Eq a) => a`, which you clearly can't in general. Perhaps your `funErrViaEither` can be rewritten to be able to take the more general type signature - but once you've given it the more specific signature, you can't use in a context that expects something more general. – Robin Zigmond Jun 13 '20 at 15:47
  • Makes total sense, thanks for the explanation. – Ulrich Schuster Jun 13 '20 at 16:29

1 Answers1

2

While you can't use funErrViaEither x :: Either SomeException b directly as a general MonadThrow m => m b, you can process the Either using pattern matching, throwing or returning as appropriate:

case funErrViaEither x of
  Left err -> throwM err
  Right y -> return y

However, I think you've probably over-wrapped your exceptions with SomeException. It's more likely that you want to peel this off when you switch from Either SomeException to MonadThrow m, so a full type-checked example would look like:

import Control.Monad.Catch

data MyCustomException = NoNegatives | NoOdds deriving (Show)
instance Exception MyCustomException

funErrViaEither :: Int -> Either SomeException Int
funErrViaEither n | n < 0     = throwM NoNegatives  -- or Left (SomeException NoNegatives)
                  | otherwise = Right $ n `div` 2

funErrViaThrow :: MonadThrow m => Int -> m Int
funErrViaThrow x =
    if even x
        then case funErrViaEither x of
               Left (SomeException err) -> throwM err  -- peel off SomeException
               Right y -> return y
        else throwM NoOdds

main = do
  print =<< funErrViaThrow 6
  (print =<< funErrViaThrow 5)
    `catch` (\err -> putStrLn $ "caught: " ++ show (err :: MyCustomException))
  print =<< funErrViaThrow (-2)
K. A. Buhr
  • 45,621
  • 3
  • 45
  • 71