packages feed

monad-effect-0.1.0.0: test/Examples.hs

module Examples where

import Control.Exception
import Control.Monad
import Control.Monad.Effect -- the EffT types and useful combinators
import Module.RS -- built in modules, a reader module and a state module
import System.IO
import qualified Data.Map  as M
import qualified Data.Text as T

-- $ our monad-effect provides **module management** and **composable exceptions**
-- it's algebraic, performant, make sense, without sacrificing purity

-- | Wraps your effectul routine into EffT monad transformer
myLookup :: (Show k, Ord k, Monad m) => k -> EffT '[SModule (M.Map k v)] '[ErrorText "Map.keyNotFound"] m v
myLookup k
  = effMaybeInWith (errorText @"Map.keyNotFound" $ " where key = " <> T.show k) -- wraps Maybe into an exception
  $ getsS (M.lookup k) -- this just returns a monadic value of type `Maybe v`

-- | This effect can run in pure monads! like Identity
lookups :: forall v m. (Monad m) => EffT '[SModule (M.Map T.Text v)] '[ErrorText "Map.keyNotFound"] m (v, v, v)
lookups = do
  foo <- myLookup "foo"  -- this will throw an exception if "foo" is not found
  bar <- myLookup "bar"  -- instead of Nothing, you get an algebraic exception `ErrorText "Map.keyNotFound"` explaining what went wrong
  baz <- myLookup "baz"  -- just like Maybe and Either, when an exception is thrown, the computation stops and immediately returns
  return (foo, bar, baz)

parse :: String -> Maybe [Double]
parse = undefined

computeAverageFromFile
  :: FilePath
  -> Eff
      '[SModule (M.Map T.Text Int)] -- this effect can read and modify a value of type (Map Text Int)
      [ IOException                 -- composable and explicit exceptions
      , ErrorText "empty-file"      -- you know what types of error this effect can produce
      , ErrorText "zero-numbers"    -- just by observing its type signature
      , ErrorText "Map.keyNotFound"
      ]
      Double                        -- return type
computeAverageFromFile fp = do
  -- | the `liftIOException :: IO a -> Eff '[] '[IOException] a` captures `IOException`
  content <- embedError . liftIOException $ readFile' fp

  -- | throw an Algebraic error instead of an exception that you have no idea
  when (null content) $ do
    effThrowIn ("file is empty" :: ErrorText "empty-file")

  -- | this `pureMaybeInWith :: In e es => e -> Maybe a -> Eff mods es a` turns a Maybe value into an ad-hoc exception type!
  parsed <- pureMaybeInWith ("parse error" :: ErrorText "parse-error") (parse content) 
    `effCatch` (\(_ :: ErrorText "parse-error") -> return [0])
  -- ^ you can catch exception and deal with it, so the error is eliminated from the list

  -- | The type system will check whether you have the module needed to perform this action
  _ <- embedEffT $ lookups @Int

  -- | The type system will force you remember that we can return an exception with an custom type `ErrorText "zero-numbers"`
  when (null parsed) $ do
    effThrowIn ("zero numbers" :: ErrorText "zero-numbers")

  return $ sum parsed / fromIntegral (length parsed)