packages feed

effet-0.3.0.2: examples/Example/Error.hs

-- | Examples of the error effect.
module Example.Error where

-- base
import Control.Monad.IO.Class (MonadIO, liftIO)
import Prelude         hiding (print)

-- hspec
import Test.Hspec (Spec, it, shouldBe)

-- effet
import Control.Effect.Error
import Control.Effect.Identity
import Control.Effect.Reader

import Hspec (print, shouldPrint)

--- Example Programs -----------------------------------------------------------

data DivideByZero = DivideByZero
  deriving (Eq, Show)

-- | Divides two numbers, may yield an error when dividing by zero.
divide :: Error DivideByZero m => Int -> Int -> m Int
divide _ 0 = throwError DivideByZero
divide x y = pure $ x `div` y

-- | Divides two numbers and returns a default value in case of zero division.
catchDivide :: Error DivideByZero m => Int -> Int -> m Int
catchDivide x y =
  catchError
    ( divide x y )
    ( \_ -> pure 1337 )

-- | Divides two numbers found in the readers, catches the division by zero
-- error and prints a String describing the result.
divideByReader :: ( Error DivideByZero m
                  , MonadIO m
                  , Reader' "numerator" Int m
                  , Reader' "divisor" Int m )
               => m ()
divideByReader = do
  n <- ask' @"numerator"
  d <- ask' @"divisor"
  catchError
    ( do r <- divide n d
         liftIO . print $ "Result: " ++ show r )
    ( \e ->
         liftIO . print $ "Error: "  ++ show e )

--- Test Cases -----------------------------------------------------------------

spec :: Spec
spec = do
  it "divides normally" $
    ( runIdentity                -- result:  Either DivideByZero Int
    . runError                   -- result:  Monad m => m (Either DivideByZero Int)
    $ divide 8 3 )               -- effects: Error DivideByZero
      `shouldBe` Right 2
  it "divides by zero" $
    ( runIdentity                -- result:  Either DivideByZero Int
    . runError                   -- result:  Monad m => m (Either DivideByZero Int)
    $ divide 5 0 )               -- effects: Error DivideByZero
      `shouldBe` Left DivideByZero
  it "catches division by zero" $
    ( runIdentity                -- result:  Either DivideByZero Int
    . runError                   -- result:  Monad m => m (Either DivideByZero Int)
    $ catchDivide 5 0 )          -- effects: Error DivideByZero
      `shouldBe` Right 1337
  it "catches normally" $
    ( runReader' @"divisor" 3    -- result:  MonadIO m => m (Either DivideByZero ()),
                                 --          unified with IO (Either DivideByZero ())
    . runReader' @"numerator" 17 -- effects: Reader' "divisor", MonadIO
    . runError                   -- effects: Reader' "numerator", Reader' "divisor", MonadIO
    $ divideByReader )           -- effects: Error DivideByZero, Reader' "numerator",
                                 --          Reader' "divisor", MonadIO
      `shouldPrint` "\"Result: 5\"\n"
  it "catches division by zero" $
    ( runReader' @"divisor" 0    -- result:  MonadIO m => m (Either DivideByZero ()),
                                 --          unified with IO (Either DivideByZero ())
    . runReader' @"numerator" 17 -- effects: Reader' "divisor", MonadIO
    . runError                   -- effects: Reader' "numerator", Reader' "divisor", MonadIO
    $ divideByReader )           -- effects: Error DivideByZero, Reader' "numerator",
                                 --          Reader' "divisor", MonadIO
      `shouldPrint` "\"Error: DivideByZero\"\n"