IO with Exceptions tracked on the type-level.
Note: The package is considered to be used with the QualifiedDo
feature, so hence the support only of GHC-9.0 and upper.
Usage example🔗
Since this is a literate haskell file, we need to specify all our language extensions and imports up front.
{-# LANGUAGE QualifiedDo #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE DeriveAnyClass #-}
It is recomended to use eio
library qualified, as it reimplements many standard functions.
import Control.Exception (Exception)
import EIO (EIO)
import qualified EIO
Let’s also define our own exception to play with:
data MyErr = MyErr
deriving stock (Show)
deriving anyclass (Exception)
The main function of our module will look like this:
main :: IO ()
= EIO.runEIO safeMain main
Let’s now write the safe main function that should not have any exceptions pushed to the actual main
function, as the list of exceptions on type level should be empty.
This means, that if we throw the exception inside but don’t handle it properly, it won’t type check, as the list of exceptions in EIO
will contain at least one element:
-- - Type error!
safeMainWrong :: EIO '[] ()
= EIO.do
safeMainWrong EIO.throw MyErr
And the error will exactly point out to the fact that the empty list is expected, but got non-empty instead:
error:
Couldn't match type: '[MyErr]
• with: '[]
Expected: EIO '[] ()
Actual: EIO '[MyErr] ()
In a stmt of a qualified 'do' block: EIO.throw MyErr
• In the expression: EIO.do EIO.throw MyErr
In an equation for ‘safeMain’: safeMain = EIO.do EIO.throw MyErr
|
| EIO.throw MyErr
xx | ^^^^^^^^^^^^^^^
In order for it to type check, we need to handle each thrown exception properly, so that you have an empty list in the end:
safeMain :: EIO '[] ()
= EIO.do
safeMain
EIO.return ()MyErr `EIO.catch` (\MyErr -> EIO.return ()) EIO.throw