03-Monad-Error-Example.purs

module ComputingWithMonads.MonadError where import Prelude import Effect (Effect) import Effect.Console (log) import Data.Identity (Identity(..)) import Data.Either (Either(..)) import Data.Maybe (Maybe(..)) import Control.Monad.Error.Class (catchError, catchJust, try, withResource) import Control.Monad.Except (Except, runExcept) import Control.Monad.Except.Trans (ExceptT(..)) main :: Effect Unit main = do runMainFunction log "=== Derived Functions ===" example_catchJust example_try example_withResource computationThatFailsWith :: forall e. e -> Except e Int computationThatFailsWith error = ExceptT ( -- A computation Identity ( -- that failed and produced an error Left error ) ) computationThatSucceedsWith :: forall e a. a -> Except e a computationThatSucceedsWith a = ExceptT ( -- A computation Identity ( -- that succeeded and produced the output Right a ) ) compute :: forall e a. Show e => Show a => Except e a -> Effect Unit compute theComputation = case runExcept theComputation of Left error -> log $ "Failed computation! Error was: " <> show error Right output -> log $ "Successful computation! Output: " <> show output runMainFunction :: Effect Unit runMainFunction = do log "catchError:" compute ( catchError (computationThatFailsWith "An error string") -- and a function that successfully handles the error (\errorString -> ExceptT (pure $ Right 5)) ) compute ( catchError (computationThatFailsWith "An error string") -- and a function that cannot handle the error successfully (\errorString -> ExceptT (pure $ Left errorString)) ) ------------------- data ErrorType = FailedCompletely | CanHandle TheseErrors data TheseErrors = Error1 | Error2 example_catchJust :: Effect Unit example_catchJust = do log "catchJust:" -- fail with an error that we ARE NOT catching... compute (catchJust ignore_FailedCompletely (computationThatFailsWith FailedCompletely) -- this function is never run because -- we ignore the "FailedCompletely" error instance handleError ) -- fail with an error that we ARE catching... compute (catchJust ignore_FailedCompletely (computationThatFailsWith (CanHandle Error1)) -- this function is run because we accept the -- error instance. It would also work if we threw `Error2` handleError ) ignore_FailedCompletely :: ErrorType -> Maybe TheseErrors ignore_FailedCompletely FailedCompletely = Nothing ignore_FailedCompletely (CanHandle error) = Just error handleError :: TheseErrors -> Except ErrorType Int handleError Error1 = ExceptT (pure $ Right 5) handleError Error2 = ExceptT (pure $ Right 6) instance Show ErrorType where show FailedCompletely = "FailedCompletely" show (CanHandle error) = "CanHandle2 (" <> show error <> ")" instance Show TheseErrors where show Error1 = "Error1" show Error2 = "Error2" ------------------- example_try :: Effect Unit example_try = do log "try: " compute' (try $ computationThatSucceedsWith 5) compute' (try $ computationThatFailsWith "an error occurred!") -- In `try`, both the error and output isntance is returned, -- thereby exposing it for usage in the do notation. To account for this, -- we've modified `compute` slightly below. -- Also, since we only specify either the error type or the output type above, -- type inference can't figure out what the other type is. So, -- it thinks that the unknown type doesn't have a "Show" instance -- and the compilation fails. -- Thus, we also specify both types below to avoid this problem. compute' :: Except String (Either String Int) -> Effect Unit compute' theComputation = case runExcept theComputation of Left error -> log $ "Failed computation! Error was: " <> show error Right e_or_a -> case e_or_a of Left e -> log $ "Exposed error instance in do notation: " <> show e Right a -> log $ "Exposed output instance in do notation: " <> show a ------------------- data Resource = Resource instance Show Resource where show x = "Resource" example_withResource :: Effect Unit example_withResource = do log "withResource: " compute ( withResource getResource cleanupResource computationThatUseResource ) getResource :: Except String Resource getResource = computationThatSucceedsWith Resource cleanupResource :: Resource -> Except String Unit cleanupResource r = -- resource is cleaned up here -- and when finished, we return unit ExceptT (pure $ Right unit) computationThatUseResource :: Resource -> Except String Int computationThatUseResource r = -- do -- use resource here to compute some value ExceptT (pure $ Right 5)