GHC try-catch for runStmt/exprType - haskell

GHC monad is an instance of ExceptionMonad and there are the two actions runStmt and exprType that interest me currently. Should one pass an invalid argument (e.g. an undefined symbol, syntactically incorrect string ...) any of the two would raise an exception, effectively terminating the session (in interpreter mode). How could one wrap these functions so that they always succeed, but produce a Maybe or Either result to account for exceptions? One thing I don't understand is, why does runStmt also throw an exception when it already supports returning one in its return value. For exprType I am seeking something like
exprType' :: GhcMonad m => String -> m (Maybe Type)
exprType' e = (fmap Just $ exprType e) `catch` (const $ return Nothing)
I am currently using GHC 7.10.2, and I am really confused with all Either/Error/... implementations of exceptional behaviour, so I do not have any piece of code that would be anywhere close to my goal :(.
The closest I got is (I try to convert the Either further to a single string, which should not be important):
typeOf :: String -> Ghc String
typeOf e = do
r <- gtry (exprType e)
return . either (const "err") (showSDocUnsafe . ppr) $ r
but then ghc complains:
No instance for (Exception b0) arising from a use of ‘gtry’
The type variable ‘b0’ is ambiguous
Note: there are several potential instances:
instance Exception ArithException -- Defined in ‘GHC.Exception’
instance Exception ErrorCall -- Defined in ‘GHC.Exception’
instance Exception SomeException -- Defined in ‘GHC.Exception’
...plus two others
In the first argument of ‘(>>=)’, namely ‘gtry (exprType e)’
In the expression:
gtry (exprType e)
>>= return . either (const "err") (showSDocUnsafe . ppr)
In an equation for ‘typeOf’:
typeOf e
= gtry (exprType e)
>>= return . either (const "err") (showSDocUnsafe . ppr)

Related

What is difference between normal functions and typeclass functions?

class (Typeable e, Show e) => Exception e where
toException :: e -> SomeException
fromException :: SomeException -> Maybe e
toException = SomeException
fromException (SomeException e) = cast e
data MyException1 = Exception1A | Exception1B
deriving (Show)
instance Exception MyException1
data MyException2 = Exception2A | Exception2B
deriving (Show)
instance Exception MyException2
It is able to define multiple exceptions. So, multiple fromException functions are able to be defined too. I think it is weird because two functions can have same name and same input.
fromException :: SomeException -> Maybe MyException1
fromException :: SomeException -> Maybe MyException2
Even if the reason why this behavior is ok is "Two functions have different type include return type (and expressions are evaluated based these all types)", it is weird, because I can't define normal functions that way.
f :: Integer -> Maybe Integer
f = cast
f :: Integer -> Maybe Char
f n = cast $ show n
What is difference between normal functions and type class functions?
The fact that you cannot define normal functions that way is the whole difference.
The whole purpose of type classes is to allow overloading - that is, defining multiple different functions with different types, but same name. And have the compiler pick the right one automatically based on types expected in the context.

How can I use `throwM` with `Except`?

There is a package transformers that features the monad Except.
This monad transformer extends a monad with the ability to throw exceptions.
There is a package exceptions that features the effect throwM.
Throw an exception. ...
So it would seem that these two should play well together. However:
λ runExcept $ throwM Overflow
<interactive>:46:13: error:
• No instance for (MonadThrow Data.Functor.Identity.Identity)
arising from a use of ‘throwM’
• In the second argument of ‘($)’, namely ‘throwM Overflow’
In the expression: runExcept $ throwM Overflow
In an equation for ‘it’: it = runExcept $ throwM Overflo
I know I can use the monad Catch. But anyway, I would like to
understand what is going on. I am not very familiar with monad transformers.
The compiler infers types this way:
Since runExcept :: Except e a -> ..., the argument of runExcept must be of type Except e a.
So, throwM Overflow :: Except e a
Except e a is a type synonym for ExceptT e Identity a.
So, throwM Overflow :: ExceptT e Identity a
Because throwM :: MonadThrow m => e -> m a, the compiler needs to find an instance of ThrowM for a type that would match ExceptT e Identity.
And look: there is such instance: MonadThrow m => MonadThrow (ExceptT e m). It matches any ExceptT e m for any m, but this m also must have an instance of MonadThrow.
Matching up the type of throwM Overflow, which is ExceptT e Identity a, and the type for which the MonadThrow instance is defined, which is Except T m, the compiler determines that m = Identity.
But wait! According to the instance definition MonadThrow m => MonadThrow (ExceptT e m), this m (which we now know to be Identity) must also have a MonadThrow instance.
So the compiler looks for that, and doesn't find it.
And displays you an error message: "No instance MonadThrow Identity"
Type class-related error messages can be vexing. The compiler doesn't always tell you the full chain of conclusions it has followed in order to reach the error. This is unfortunate, but whatchagonnado.
But the underlying problem here is that Except and throwM aren't actually compatible. That is to say, throwM throws errors in a different sense than Except contains errors. They're two different error-related mechanisms. In order to throw an Except-compatible error, use its own throwE. This should work:
> runExcept $ throwE Overflow
Left Overflow
As far as I understand at the moment, the error-handling landscape in Haskell hasn't yet settled down to a manageable state. We have Except, which got generalized to ExceptT, and then we have throw, throwTo, throwIO, throwSTM, throwE, throwError, throwM, and all of those have variants from different libraries and under different monads. Plus, the arrival of UnliftIO has complicated things even further. It sort of looks like ExceptT will be going away as a result.

How to use Handlers that don't have SomeException as their argument type

When using Control.Monad.Throw (i.e. exceptions package), something that confuses me that it seem that all my my throwing and catching has to work with SomeException.
E.g.
value :: Either ExitCode String
value = throwM $ ExitFailure 23
This looks to me like it should compile, because throwM :: (Exception e, MonadThrow m) => e -> m a, and ExitCode has an instance of Exception. Even this doesn't compile:
value :: Exception e => Either e String
value = throwM $ ExitFailure 23
In fact, it only compiles when I change the signature to SomeException. I know the Exception type class has a special place re
In the documentation for Control.Exception I could see examples where they use catches with handlers of signature ArithException -> m a or similiar. I tested it and it worked.
Is this not possible when using exceptions?
EDIT
The error messages are either:
Couldn't match type ExitCode with SomeException arising from a use of throwM
or
Couldn't match type e with SomeException arising from a use of throwM
The behavior you are seeing is from the type signature of throwM:
throwM :: (Exception e, MonadThrow m) => e -> m a
And the instance for Either, which is essentially:
MonadThrow (Either SomeException)
This makes throwM:
throwM :: (Exception e) => e -> Either SomeException a
throwM for Either SomeException can take any Exception e.
However, there is no MonadThrow instance for Either ExitCode, or for forall e. Exception e => Either e.
The problem is that there isn't really a way to write an instance that's polymorphic for all e. Imagine having an instance
Exception q => MonadThrow (Either q)
This would make throwM:
throwM :: (Exception e, Exception q) => e -> Either q a
which means you have to be able to take any e and convert it into any q, which is not possible using the Exception typeclass alone.
Imagine also if there was a MonadThrow instance for Either ExitCode. That would make the type signature for throwM:
throwM :: Exeption e => e -> Either ExitCode a
which you can probably see is clearly nonsensical, since there are many instances of Exception that cannot be coerced to an ExitCode. (Try writing a function with that type signature if you don't believe me!)
If you just want short-circuting exception behavior for Either, consider:
Just use Left instead of throwM, and pattern matching for catch. If you really want to use Exception still, you can use fromException to attempt to coerce a SomeException into an Exception instance of your choice.
You can use the exceptions package, and catching will still work as long as you must assume that the exception could be any instance (SomeException)
Use MonadError and ExceptT from mtl, if you want some sort of polymorphic interface with catching ability

Why exception is not catching by `catch`?

Why does not this catch exception of conversion conv B3 ?!
import qualified Control.Monad.Catch as E
data A = A1|A2 deriving (Enum, Show)
data B = B1|B2|B3 deriving (Enum, Show)
conv b = safeConv
where
catchError e = Left e
safeConv = (Right $ (toEnum $ fromEnum b :: A)) `E.catch` catchError
I got:
Right *** Exception: toEnum{A}: tag (2) is outside of enumeration's range (0,1)
CallStack (from HasCallStack):
error, called at xxx.hs:227:26 in main:Main
Haskell exceptions are somewhat different than, say, Java or C++: "true" exceptions work in IO monad, and then there is an imitation of exceptions via pure means, as in ExceptT.
The toEnum function throws the first kind - IO exceptions, - which cannot be caught in pure code. They fly out to the nearest IO place, which in your case is apparently GHCi.
In order to catch such exceptions, you first need to wrap the throwing expression in IO via Control.Exception.evaluate. Then you can catch such exceptions with catch, or, if you just want to convert it to an Either exception A (as you seem to be doing), there is an app for that! - it's called try.
Further, when using either catch or try, you need to specify the specific type of the exception you're trying to catch. But it is possible to catch all exceptions regardless of type by using the existential type SomeException.
So, wrapping up all of that, we get this code:
import qualified Control.Exception as E
data A = A1|A2 deriving (Enum, Show)
data B = B1|B2|B3 deriving (Enum, Show)
conv :: Enum b => b -> IO (Either E.SomeException A)
conv b = E.try . E.evaluate . toEnum $ fromEnum b
NOTE 1: The type annotation on conv is necessary in order to specify E.SomeException as the type of exception to catch. Without it, GHC will complain that the type of exception is ambiguous.
NOTE 2: Because our type annotation on conv already specifies the target type A, the type annotation on toEnum $ fromEnum b is no longer necessary.
NOTE 3: I have replaced your import of Control.Monad.Catch with Control.Exception, because that's where evaluate and SomeException are.
I'll leave it here if someone needs a solution. To keep function pure, conversion should be:
unsafeConvEnum :: (Enum a, Enum b) => a -> b
unsafeConvEnum = toEnum . fromEnum
convEnum :: (Enum a, Enum b) => a -> Maybe b
convEnum e = unsafePerformIO conv'
where onError (_::SomeException) = pure Nothing
conv' = (Just <$> evaluate (unsafeConvEnum e)) `catch` onError
No any IO :)

Can a custom guard mechanism be defined in Haskell?

If you look at the example for catches:
f = expr `catches` [Handler (\ (ex :: ArithException) -> handleArith ex),
Handler (\ (ex :: IOException) -> handleIO ex)]
It looks like catches has defined a custom mechanism to match on patterns (the two exception types). Am I mistaken, or can this be generalized to allow one to define a function that can take lambda functions that match on a certain pattern?
Edit: FYI below is the GHC source for catches. If someone can shed some light on how this works it would be great.
catches :: IO a -> [Handler a] -> IO a
catches io handlers = io `catch` catchesHandler handlers
catchesHandler :: [Handler a] -> SomeException -> IO a
catchesHandler handlers e = foldr tryHandler (throw e) handlers
where tryHandler (Handler handler) res
= case fromException e of
Just e' -> handler e'
Nothing -> res
This is the Scoped Type Variables GHC extension at work. Follow the link to learn more.
Basically, you define an assertion about type that have to be met by the patter before it can match. So, yeah, it is akin to guards, but not completely so.
How this particular example works? Dive into sources of "base" library to find out that:
class (Show e) => Exception e where
toException :: e -> SomeException
fromException :: SomeException -> Maybe e
data SomeException = forall e . Exception e => SomeException e
instance Exception IOException where
toException = IOException
fromException (IOException e) = Just e
fromException _ = Nothing
instance Exception ArithException where
toException = ArithException
fromException (ArithException e) = Just e
fromException _ = Nothing
We see that IOException and ArithException are different types implementing the typeclass Exception. We also see that toException/fromException is a wrapping/unwrapping mechanism that allows one to convert values of type Exception to/from values of types IOException, ArithException, etc.
So, we could've written:
f = expr `catches` [Handler handleArith,
Handler handleIO]
handleArith :: ArithException -> IO ()
handleArith ex = ....
handleIO :: IOException -> IO ()
handleIO ex = ....
Suppose that IOException happens. When catchesHandler processes first element of the handlers list, it calls tryHandler, which calls fromException. From the definition of tryHandler it follows that return type of the fromException should be the same as argument of handleArith. On the other hand, e is of type Exception, namely - (IOException ...). So, the types play out this way (this is not a valid haskell, but I hope that you get my point):
fromException :: (IOException ...) -> Maybe ArithException
From the instance Exception IOException ... it follows immediately that the result is Nothing, so this handler is skipped. By the same reasoning the following handler would be called, because fromException would return (Just (IOException ...)).
So, you've used type signatures of handleArith and handleIO to specify when each of them would be called, and fromException/toException made sure that it happened this way.
If you want to, you could also constraint types of handleIO and handleArith inside the definition of f, using scoped type variables. Arguably, this could give you better readability.
Finalizing, Scoped Type Variables are not a primary players here. They are just used for convenience. Main machinery for playing this kind of tricks is fromException/toException and friends. Scoped Type Variables just allow you to have syntax which more closely resemble guard patterns.
case () of
()| foo expr1 -> handleFooCase
| bar expr2 -> handleBarCase
| otherwise -> blah

Resources