packages feed

monad-effect-0.2.3.0: README.md

# monad-effect - a lightweight, fast, algebraic effect system

This project is still in experimental beta and may evolve quickly. Feedback and contributions are very welcome.

`monad-effect` gives you:

- a single, optimisation-friendly monad transformer `EffT` that combines **Reader, State and algebraic errors**;
- **modules** as the unit of effect (e.g. reader, state, logging, database, HTTP, metrics);
- **explicit, composable error lists** instead of using `Text` / `SomeException` ; and
- performant effect stacks, without sacrificing purity.

Most users will work with the `Eff` / `EffT` type aliases and the built-in reader/state modules (`RModule`, `SModule`) and define their own modules around them.

---

- [Project Intuition](#project-intuition)
- [Key Features](#key-features)
  - [Algebraic exceptions](#algebraic-exceptions)
  - [Purity](#purity)
  - [Flexible and modular](#flexible-and-modular)
- [Core Types and Abstractions](#core-types-and-abstractions)
  - [EffT, Eff and EffT'](#efft-eff-and-efft)
  - [Result and EList - algebraic error lists](#result-and-elist---algebraic-error-lists)
  - [Named error types - ErrorText, ErrorValue, MonadExcept](#named-error-types---errortext-errorvalue-monadexcept)
  - [Modules and the system view](#modules-and-the-system-view)
  - [Built-in Reader and State modules - RModule and SModule](#built-in-reader-and-state-modules---rmodule-and-smodule)
  - [RS.Class - `MonadReadOnly`, `MonadReadable`, `MonadStateful`](#rsclass---monadreadonly-monadreadable-monadstateful)
- [Getting Started - Examples](#getting-started---examples)
  - [Quick start - algebraic state and errors](#quick-start---algebraic-state-and-errors)
  - [Embedding and reshaping effects](#embedding-and-reshaping-effects)
  - [Scoped module initialisation](#scoped-module-initialisation)
  - [Large application - a bot with many modules](#large-application---a-bot-with-many-modules)
  - [Example - database access](#example---database-access)
- [Selected API Reference](#selected-api-reference)
  - [Core monad and runners](#core-monad-and-runners)
  - [Error machinery](#error-machinery)

## Project Intuition

At a high level you can think of:

```haskell
newtype EffT mods es m a =
  EffT { unEffT :: SystemRead mods -> SystemState mods -> m (Result es a, SystemState mods) }
```

as:

- **one layer of `Reader`** over a *heterogeneous* environment `SystemRead mods`,
- **one layer of `State`** over a *heterogeneous* state `SystemState mods`, and
- a **typed, algebraic error channel** `Result es a`, where `es :: [Type]` is a *type-level list* of error types.

You explicitly say:

- which **modules** (`mods`) your effect depends on (configuration, mutable state, handles, etc.);
- which **errors** (`es`) it can throw (e.g. `IOException`, `ErrorText "http"`, `MyDomainError`); and
- you get back both a **result** and the **final module state**.

Typical use-cases:

- replace one or more layers of `ReaderT`, `StateT`, `ExceptT` or even more equivalent ones, by a single `EffT` with a small list of modules and error types;
- easily **add or remove error types** (`effCatch`, `effCatchIn`, `errorToEitherAll`, ... );
- run the same effect in **pure monads** (e.g. `Identity`) and in `IO`; and
- pass around **large module stacks** in real applications while keeping the types informative and composable.

---

## Key Features

### Algebraic exceptions

In classic Haskell (and in other languages like Rust), exceptions are often encoded algebraically as `Maybe` or `Either`:

- `Maybe a` is composable but not very informative - you lose any structured information about *why* something failed.
- `Either e a` carries an error payload, but composing multiple distinct `Either e_i a` values across a codebase tends to either:
  - collapse everything to a common super-type like `Text`/`SomeException` (and then you lose the ability to catch specific errors in a principled way, and loses the ability to declare that some of them won't happen); or
  - nest `Either e0 (Either e1 (Either e2 a))`, which is unergonomic.
- `ExceptT e m a` has the same compositional issues, *and* the transformer order matters:
  - `StateT s (ExceptT e m) a ~ s -> m (Either e (a, s))` - once an exception is thrown, both `a` and the intermediate state are lost / rolled-back.
  - `ExceptT e (StateT s m) a ~ s -> m (Either e a, s)` - the state up to the exception point is preserved, which is often what you actually want.

`monad-effect` addresses these issues by:

- using a **type-level list of error types** `es :: [Type]` and a non-empty sum `EList es` to track exactly which error types can occur; and
- using `Result es a` as the algebraic error carrier (see the formal definition below), which behaves like `Either (EList es) a` and collapses to `a` when `es ~ '[]`.

The underlying representation

```haskell
SystemRead mods
-> SystemState mods
-> m (Result es a, SystemState mods)
```

means that module state is preserved when an algebraic exception is thrown (like `ExceptT e (StateT s m) a`), rather than discarded.

You can throw algebraic errors into the list (`effThrowIn` / `effThrow`), catch all errors (`effCatchAll`), or catch and *remove* a specific error type (`effCatchIn`) so that the remaining computation provably no longer produces that error.

### Purity

Instead of reaching for `IORef` / `TVar` for every bit of mutable state, you can also choose to keep states *pure* and model them as part of your self-defined modules. It's a design choice you can make : some effect systems force you into `IO`. While for concurrency programs you need `TVar`s, but we should have the ability to choose pure state where appropriate because we love purity.

In particular the library provides two built-in modules:

- `SModule s` - a module holding a pure state of type `s`;
- `RModule r` - a module holding a read-only value of type `r`.

These integrate with the `MonadStateful` / `MonadReadable` classes and provide the familiar `getS` / `putS` / `modifyS` / `askR` / `localR` APIs, while still participating in the larger module stack.

Using modules, you can:

- keep configuration, pure in-memory state, handles, and effect interpreters in a single typed module stack; and
- run the same code in `Identity` for pure tests, or in `IO` for production, by choosing appropriate runners.
- Use it to run stateful tight computations without `IO` overhead (which GHC can optimize very well).

Template Haskell helpers in `Module.RS.QQ` (`makeRModule`, `makeRSModule`) make it easy to generate simple reader/state modules with minimal boilerplate. However, right now you are expected to write a lot of modules by hand as they are much more flexible.

Besides tight calculation that benefits from pure states, here is another example function that benefits from pure state: the function can be ran as a pure function with pure logging effect (writer / no-logging), or in IO whose logging prints to console/file.

```haskell
eventHandler
  :: (Monad pureMonad)
  => InputEvent
  -> EffT
      '[ Logging pureMonad LogData -- ^ We will use logging to generate diagnostics
      , EventState                -- ^ We need to read and update the state
      ]
      '[ ErrorText "not-allowed"
      ]
      pureMonad
      [OutputCommands]      -- ^ Output commands from the event module
```

### Flexible and modular

Because both modules and errors are tracked at the type level:

- `EffT '[] '[] m a` is isomorphic to `m a`;
- `EffT '[] '[e] m a` is isomorphic to `m (Either e a)`;
- `EffT '[] es  m a` is isomorphic to `m (Result es a)`.

You can:

- **eliminate modules** once you have their inputs, using e.g.:

  ```haskell
  runEffTOuter
    :: (ConsFDataList c (mod : mods), ConsFData1 c mods, Monad m)
    => ModuleRead mod
    -> ModuleState mod
    -> EffT' c (mod : mods) es m a
    -> EffT' c mods es m (a, ModuleState mod)

  runEffTOuter_
    :: ...
    => ModuleRead mod -> ModuleState mod
    -> EffT' c (mod : mods) es m a
    -> EffT' c mods es m a
  ```

  and similarly `runEffTIn`/`runEffTIn_` to drop an inner module.

- **embed smaller effects inside larger ones**, changing only modules (`embedMods`), only errors (`embedError`), or both (`embedEffT`).
- move fluidly between `EffT` and more conventional forms via runners and converters like `runEffT00`, `runEffT01`, `errorToEither`, `errorToEitherAll`, `errorToMaybe`, and the `effEither*` / `effMaybe*` family of helpers.

The result is a small set of primitives that scale well to large applications with many modules such as database access, HTTP clients, metrics, logging, and domain-specific state.

### Performant

The core transformer `EffT'` is one single layer and the `FData` data family is designed to be optimisation-friendly. The fact that we did not use complicated data structure nor `IO` for storing data means GHC can optimize, inline the data constructors, utilizing purity.

This gives blazingly fast performance, benchmarks (countdown, local state, deep catch) shows `monad-effect` is top-1 in most of the benchmarks, top-2 in some other ones. (See the last section for some benchmark pictures).

It is completely feasible to use `monad-effect` for pure single-threaded stateful tight loops with little or no extra overhead.

## Core Types and Abstractions

This section spells out the main types and how they fit together. All snippets in this section are taken directly from the library, except where explicitly marked as simplified.

### EffT, Eff and EffT'

The real core transformer is `EffT'`; `Eff` and `EffT` are its specialised aliases using the optimised `FData` container.

```haskell
-- | EffTectful computation, using modules as units of effect.
-- The tick indicates the polymorphic type 'c', the data structure
-- used to store the modules (usually 'FData' or 'FList').
newtype EffT' (c    :: (Type -> Type) -> [Type] -> Type)
              (mods :: [Type])
              (es   :: [Type])
              (m    :: Type -> Type) a
  = EffT'
      { unEffT' :: SystemRead  c mods
                -> SystemState c mods
                -> m (Result es a, SystemState c mods)
      }

-- Recommended, specialised aliases (use these in normal code):
type Eff   mods es  = EffT' FData mods es IO
type EffT  mods es  = EffT' FData mods es
type Pure  mods es  = EffT' FData mods es Identity
type In    mods es  = In'   FData mods es

-- Error-enhanced IO and ExceptT-like transformer
type ResultT  es m  = EffT' FData '[] es m
type IO'      es    = EffT' FData '[] es IO
```

Intuitively:

- `mods :: [Type]` - type-level list of **modules** (effects);
- `es   :: [Type]` - type-level list of **error types** that this computation may throw;
- `m` - the **base monad**; and
- `c` - the container type (`FData` by default) used to hold the module environments and states.

The library provides runners such as:

```haskell
runEffT
  :: Monad m
  => SystemRead  c mods
  -> SystemState c mods
  -> EffT' c mods es m a
  -> m (Result es a, SystemState c mods)

-- No modules
runEffT0
  :: (Monad m, ConsFNil c)
  => EffT' c '[] es m a
  -> m (Result es a)

-- No modules, no errors
type NoError = '[]

runEffT00
  :: (Monad m, ConsFNil c)
  => EffT' c '[] NoError m a
  -> m a

-- No modules, single error, exposed as Either
runEffT01
  :: (Monad m, ConsFNil c)
  => EffT' c '[] '[e] m a
  -> m (Either e a)
```

There are much more runners and combinators, see actual haddock documentation.

`EffT` and `Eff` can therefore be specialised to behave like a more flexible `ExceptT`:

```haskell
-- A flexible replacement for 'ExceptT es m'
type ResultT es m = EffT '[] es m
```

### Result and EList - algebraic error lists

Errors are represented by the `Result` and `EList` types:

```haskell
-- | Sum of types, non-empty by construction.
data EList (ts :: [Type]) where
  EHead :: !t          -> EList (t : ts)
  ETail :: !(EList ts) -> EList (t : ts)

-- | Error-aware result.
data Result (es :: [Type]) (a :: Type) where
  RSuccess :: a         -> Result es a
  RFailure :: !(EList es) -> Result es a

resultNoError :: Result '[] a -> a
resultNoError (RSuccess a) = a
```

Important facts:

- `Result es a` behaves like `Either (EList es) a`.  
- When `es ~ '[]`, `Result '[] a` is *effectively just `a`* (`resultNoError` witnesses this).
- `EList es` is a **non-empty** sum type: you cannot construct an `EList '[]`, so a `RFailure` always carries *one of the listed error types*.

### Named error types - ErrorText, ErrorValue, MonadExcept

To avoid defining a new ADT for every small error case, the library provides ad-hoc, named error wrappers:

```haskell
-- | A named textual error.
newtype ErrorText (s :: k) = ErrorText Text
  deriving newtype (IsString)

-- | Use type application 'errorText @"http" "text"'
errorText :: forall s. Text -> ErrorText s
errorText = ErrorText

-- | A named error that wraps an arbitrary value.
newtype ErrorValue (a :: k) (v :: Type) = ErrorValue v

-- | Type application helper
errorValue :: forall s v. v -> ErrorValue s v
errorValue = ErrorValue

-- | MonadExcept without a functional dependency,
-- so a monad can throw multiple error types.
class Monad m => MonadExcept e m where
  throwExcept :: e -> m a
```

Some useful instances:

- `instance Exception e => MonadExcept e IO`
- `instance MonadExcept e (Either e)`
- `instance Monad m => MonadExcept e (ExceptT e m)`
- `instance KnownSymbol s => MonadExcept (ErrorText s) (Either (Text, Text))`

This makes it very convenient to use `ErrorText "http"`, `ErrorText "decode"` etc. in larger codebases.

### Modules and the system view

Modules are the **unit of effect**. A module describes:

- what read-only data it exposes (`ModuleRead`); and
- what mutable state it keeps (`ModuleState`).

```haskell
class Module mod where
  data ModuleRead  mod :: Type
  data ModuleState mod :: Type

-- Modules that may be part of a "system"
class Module mod => SystemModule mod where
  data ModuleEvent    mod :: Type
  data ModuleInitData mod :: Type

-- System-wide containers (usually backed by 'FData')
type SystemRead     c mods = c ModuleRead  mods
type SystemState    c mods = c ModuleState mods
type SystemEvent      mods = UList ModuleEvent    mods
type SystemInitData c mods = c ModuleInitData     mods
```

Within `EffT'` you can access the data families, module reads and states using the provided helpers:

```haskell
-- | Synonyms
queryModule  :: (Monad m, In' c mod mods, Module mod)
             => EffT' c mods es m (ModuleRead  mod)
askModule    :: (Monad m, In' c mod mods, Module mod)
             => EffT' c mods es m (ModuleRead  mod)

queriesModule, asksModule
  :: (Monad m, In' c mod mods, Module mod)
  => (ModuleRead mod -> a)
  -> EffT' c mods es m a

getModule    :: (Monad m, In' c mod mods, Module mod)
             => EffT' c mods es m (ModuleState mod)
getsModule   :: (Monad m, In' c mod mods, Module mod)
             => (ModuleState mod -> a) -> EffT' c mods es m a

putModule    :: (Monad m, In' c mod mods, Module mod)
             => ModuleState mod -> EffT' c mods es m ()
modifyModule :: (Monad m, In' c mod mods, Module mod)
             => (ModuleState mod -> ModuleState mod)
             -> EffT' c mods es m ()
```

The `SystemModule`/`ModuleEvent`/`ModuleInitData` pieces are primarily used by higher-level orchestration helpers (e.g. scoped initialisation via `withModule`).

### Built-in Reader and State modules - RModule and SModule

The `Module.RS` module gives you ready-made reader/state modules and helpers to integrate existing `ReaderT`/`StateT` code.

```haskell
-- Reader module
data RModule (r :: Type)

instance Module (RModule r) where
  newtype ModuleRead  (RModule r) = RRead  { rRead  :: r }
  data    ModuleState (RModule r) = RState deriving (Generic, NFData)

-- State module
data SModule (s :: Type)

instance Module (SModule s) where
  data    ModuleRead  (SModule s) = SRead
  newtype ModuleState (SModule s) = SState { sState :: s }
    deriving newtype (Generic, NFData)
```

Convenience helpers:

```haskell
-- Reader-like interface
askR   :: (Monad m, In' c (RModule r) mods) => EffT' c mods errs m r
asksR  :: (Monad m, In' c (RModule r) mods) => (r -> a) -> EffT' c mods errs m a
localR :: (Monad m, In' c (RModule r) mods)
       => (r -> r) -> EffT' c mods errs m a -> EffT' c mods errs m a

-- State-like interface
getS   :: (Monad m, In' c (SModule s) mods) => EffT' c mods errs m s
getsS  :: (Monad m, In' c (SModule s) mods) => (s -> a) -> EffT' c mods errs m a
putS   :: (Monad m, In' c (SModule s) mods) => s -> EffT' c mods errs m ()
modifyS:: (Monad m, In' c (SModule s) mods) => (s -> s) -> EffT' c mods errs m ()
```

You also get helpers to run and embed modules:

```haskell
runRModule   :: (ConsFDataList c (RModule r : mods), Monad m)
             => r -> EffT' c (RModule r : mods) errs m a
             -> EffT' c mods errs m a

runSModule   :: (ConsFDataList c (SModule s : mods), Monad m)
             => s -> EffT' c (SModule s : mods) errs m a
             -> EffT' c mods errs m (a, s)

runSModule_  :: (ConsFDataList c (SModule s : mods), Monad m)
             => s -> EffT' c (SModule s : mods) errs m a
             -> EffT' c mods errs m a
```

Integrating with `ReaderT`/`StateT` (more in `Module.RS`):

```haskell
liftReaderT :: forall r mods errs m c a. (Monad m, In' c (RModule r) mods)
            => ReaderT r m a
            -> EffT' c mods errs m a

embedReaderT :: forall r mods errs m c a. (Monad m, In' c (RModule r) mods)
             => ReaderT r (EffT' c mods errs m) a
             -> EffT' c mods errs m a
```

### RS.Class - `MonadReadOnly`, `MonadReadable`, `MonadStateful`

`Control.Monad.RS.Class` defines type-class interfaces similar to `MonadReader`/`MonadState`, but *without* functional dependencies, so a single monad can have many readable and stateful values:

```haskell
class Monad m => MonadReadOnly r m where
  query   :: m r
  queries :: (r -> r') -> m r'

class MonadReadOnly r m => MonadReadable r m where
  local :: (r -> r) -> m a -> m a

class Monad m => MonadStateful s m where
  get    :: m s
  put    :: s -> m ()
  gets   :: (s -> a) -> m a
  modify :: (s -> s) -> m ()
```

Instances are provided for `EffT'` and for monad transformers.

---

## Getting Started - Examples

This section focuses on how to *use* the core abstractions.

### Quick start - algebraic state and errors

A small (made-up) example that:

- stores a `Map` as a state module,
- throws a named `ErrorText "Map.keyNotFound"` when a key is missing,
- and computes an average from a file.

```haskell
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE OverloadedStrings #-}

module Examples where

import Control.Exception (IOException)
import Control.Monad
import Control.Monad.Effect        -- EffT types and combinators
import Module.RS                   -- RModule / SModule helpers
import System.IO
import qualified Data.Map  as M
import qualified Data.Text as T

-- | Wrap a lookup into an algebraic error instead of 'Maybe'.
myLookup
  :: (Show k, Ord k, Monad m)
  => k
  -> EffT '[SModule (M.Map k v)] '[ErrorText "Map.keyNotFound"] m v
myLookup k =
  effMaybeInWith -- this converts a (Maybe v) return value into an algebraic error
    (errorText @"Map.keyNotFound" $
       " where key = " <> T.pack (show k)) -- using this converter function
    (getsS (M.lookup k))   -- :: EffT '[SModule (M.Map k v)] es m (Maybe v)

-- | This effect can run in pure monads like 'Identity' as well as 'IO'.
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"
  bar <- myLookup "bar"
  baz <- myLookup "baz"
  pure (foo, bar, baz)

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

computeAverageFromFile
  :: FilePath
  -> Eff
       '[SModule (M.Map T.Text Int)]
       [ IOException
       , ErrorText "empty-file"
       , ErrorText "zero-numbers"
       , ErrorText "Map.keyNotFound"
       ]
       Double
computeAverageFromFile fp = do
  -- Capture 'IOException' from 'readFile' as an algebraic error.
  content <- embedError . liftIOException $ readFile' fp

  when (null content) $
    effThrowIn (errorText @"empty-file" "file is empty")

  -- Turn a 'Maybe' into an ad-hoc algebraic error and immediately handle it.
  parsed <- pureMaybeInWith (errorText @"parse-error" "parse error") (parse content)
              `effCatch` \(_ :: ErrorText "parse-error") ->
                pure [0]

  -- Use another effect that requires the same module.
  _ <- embedEffT (lookups @Int)

  when (null parsed) $
    effThrowIn (errorText @"zero-numbers" "zero numbers")

  pure $ sum parsed / fromIntegral (length parsed)
```

You can run such effects in various ways. For example, using the state module runner from `Module.RS` and `runEffT01`:

```haskell
import Control.Monad.Effect
import Module.RS
import qualified Data.Map as M

runCounter
  :: Int
  -> Eff '[SModule Int] '[ErrorText "zero"] IO ()
  -> IO (Either (ErrorText "zero") ((), Int))
runCounter initial action =
  runEffT01 (runSModule initial action)
```

Here:

- `runSModule` eliminates the `SModule` from the module list while threading the state, and
- `runEffT01` eliminates the remaining error list as an `Either`.

### Embedding and reshaping effects

Sometimes you want to run a smaller effect inside a bigger one; or change modules while keeping the error list, or vice versa. The library provides:

```haskell
-- Embed a smaller effect into a larger one (modules and/or errors).
embedEffT
  :: (SubList c mods mods', SubListEmbed es es', Monad m)
  => EffT' c mods es  m a
  -> EffT' c mods' es' m a

-- Only change the module list.
embedMods
  :: (Monad m, ConsFDataList c mods', SubListEmbed es es, SubList c mods mods')
  => EffT' c mods es m a
  -> EffT' c mods' es m a

-- Only change the error list.
embedError
  :: (Monad m, SubList c mods mods, SubListEmbed es es')
  => EffT' c mods es m a
  -> EffT' c mods es' m a
```

This is very useful when you have a reusable component that only needs a subset of modules.

### Scoped module initialisation

For scoped initialisation and teardown, many projects define higher-level wrappers around the `Loadable`/`withModule` pattern. A (simplified) example from a real project:

```haskell
runApp
  :: EffT
       '[ RModule ProxyState
        , RModule ProxySettings
        , PrometheusMan
        , LoggingModuleB
        ]
       NoError
       IO
       ()
  -> IO ()
runApp app = do
  -- Parse options, set up logging and metrics, etc...
  opts :: ProxyOptions Unwrapped <- unwrapRecord "Haskell Proxy Server"
  case optionsToSettings opts of
    Nothing       -> putStrLn "Invalid options provided."
    Just settings -> do
      -- Initialise loggers (implementation-specific).
      baseStdLogger  <- ...
      baseFileLogger <- ...
      let logger = baseStdLogger <> baseFileLogger

      -- Compose initialisation modules inside Eff, then eliminate back to IO.
      runEffT00 $
        withLoggerCleanup logger $
        (if settings.setPrometheus
           then withPrometheus ...
           else withNoPrometheusMan) $
        do
          state <- initializeState settings
          runRModule settings $
          runRModule state    $
          app
```

The exact set of modules (`PrometheusMan`, `LoggingModuleB`, ...) is either project-specific or reusable components you can build between projects, but the pattern is always:

> build an `EffT` stack of modules, run your application logic there, then eliminate modules and errors with the provided runners.

### Large application - a bot with many modules

In a larger system you might have many modules and a domain-specific error list. (Taken from a real-world bot application.)

```haskell
-- The modules loaded into the bot
type Mods =
  [ LogDatabase
  , AsyncModule
  , ProxyWS
  , CronTabTickModule
  , StatusMonitorModule
  , CommandModule
  , RecvSentCQ
  , MeowActionQueue
  , SModule BotConfig
  , SModule WholeChat
  , SModule OtherData
  , MeowConnection
  , BotGlobal
  , ConnectionManagerModule
  , MeowDataDb
  , MeowCoreDb
  , PrometheusMan
  , LoggingModule
  ]

-- Exceptions that require restarting the bot
type MeowErrs =
  '[ ErrorText "recv_connection"
   , ErrorText "send_connection"
   , ErrorText "meowCoreDb"
   ]

type MeowT mods m = EffT mods MeowErrs m
type Meow         = MeowT Mods IO
```

The bot initialisation then becomes a composition of `withX` helpers and `run*Module` runners:

```haskell
runBot
  :: BotInstance -- ^ Initial bot configuration
  -> Meow a -- ^ the bot loop function
  -> EffT
       '[ BotGlobal
        , ConnectionManagerModule
        , MeowDataDb
        , MeowCoreDb
        , PrometheusMan
        , LoggingModule
        ]
       '[ErrorText "meowCoreDb"]
       IO
       ()
runBot bot meow = do
  -- embed some code into a larger context
  botModule <- embedEffT $ botInstanceToModule bot

  -- Build additional configuration/state modules...
  -- initialise counters, status, etc.
  -- not relevant for this example.

  embedNoError -- requires the scope inside to have 'NoError' and embeds it to a larger error context
    $ effAddLogCat' (LogCat botModule.botId) -- adds a logging category to logs within the scope
    $ ( case botRunFlag bot of
          RunClient addr port -> void . withClientConnection addr port
          RunServer addr port ->        withServerConnection addr port
      ) -- running connection
    $ (\app -> do
        AllData wc bc od <- embedEffT $ initAllData botconfig
        runSModule_ od $ runSModule_ wc $ runSModule_ bc $ app
      ) -- domain state
    $ withMeowActionQueue
    $ withRecvSentCQ
    $ withModule CommandModuleInitData
    $ withModule (StatusMonitorModuleInitData meowStat)
    $ maybe id (\init -> withWatchDog init . const) mWatchDogInit
    $ withCronTabTick
    $ withProxyWS (ProxyWSInitData [(add, ip) | ProxyFlag add ip <- bot.botProxyFlags])
    $ withAsyncModule
    $ withLogDatabase
    $ meow
```

The details of `withMeowActionQueue`, `withRecvSentCQ`, `withProxyWS`, etc. are application-specific, but the pattern is always:

- each `withX` introduces one or more modules into the `EffT` stack and arranges their initial `ModuleRead`/`ModuleState`; and
- the final `Meow` computation runs in a rich module environment, with its error list (`MeowErrs`) tracking only the domain errors that matter at that layer.

### Example - database access

You can also use the type system to enforce that certain low-level errors are handled at the call-site. A typical pattern is to wrap a database action so it:

- requires a specific module to be present; and
- requires a specific error to be in the error list:

```haskell
runMeowDataDB
  :: ( In'   c MeowDataDb mods
     , InList (ErrorText "meowDataDb") es
     )
  => ReaderT SqlBackend IO b
  -> EffT' c mods es IO b
```

Because `ErrorText "meowDataDb"` is in the error list, callers must either:

- keep that error in their own `es` (and propagate it upward), or
- explicitly catch and handle it. If they forget, GHC will report a type error.

For example:

```haskell
-- Forgetting to handle 'ErrorText "meowDataDb"' here leads to a type error.
fetchBlockMessages :: BotId -> ChatId -> ChatBlockSpan -> Meow [ChatMessage]
fetchBlockMessages bid cid span = do
  entities <-
    runMeowDataDB (selectList [...conditions based on 'bid', 'cid', 'span'...])
      `effCatch` \(_ :: ErrorText "meowDataDb") ->
        pure []
  pure (map entityVal entities)
```

Here `effCatch` both catches the database error and removes `ErrorText "meowDataDb"` from the error list, so the rest of `fetchBlockMessages` no longer has to account for it.

---

## Selected API Reference

This section is not exhaustive. It highlights key exports; for full details, please consult the Haddock documentation.

### Core monad and runners

- `Eff mods es a`, `EffT mods es m a`, `Pure mods es a`, `ResultT es m a`, `IO' es m a`
- `runEffT`, `runEffT_`, `runEffT0`, `runEffT00`, `runEffT01`, `runResultT`
- `runEffTOuter`, `runEffTOuter'`, `runEffTOuter_` - eliminate the *outermost* module while supplying its `ModuleRead`/`ModuleState`
- `runEffTIn`, `runEffTIn'`, `runEffTIn_` - eliminate an *inner* module identified by its type
- `replaceEffTIn` - replace a module with another, using custom conversion functions
- `NoError`, `checkNoError`, `declareNoError`, `embedNoError`
- `applyErrors`, `applyMods` - helpers that expose `es` / `mods` to type applications without changing the value

### Error machinery

- Types: `Result es a`, `EList es`, `SystemError`
- Named wrappers: `ErrorText s`, `ErrorValue s v`, `errorText`, `errorValue`
- Throwing:
  - `effThrowIn`, `effThrow` - throw an error
  - `effThrowEList`, `effThrowEListIn` - throw multiple errors via `EList`
  - `MonadExcept e m` integration (e.g. via `tryAndThrow`, `tryAndThrowText`)
- Catching:
  - `effCatch` - catch the *first* error in the list
  - `effCatchIn` - catch a *specific* error type and remove it from the list
  - `effCatchAll` - catch all algebraic errors as an `EList es`
- Converting errors:
  - `errorToEither`, `errorToEitherAll`, `eitherAllToEffect`
  - `errorInToEither`, `errorToMaybe`, `errorInToMaybe`, `errorToResult`
  - `mapError` - map one error list into another
- Turning `Either` / `Maybe` into errors:
  - `effEitherWith`, `effEither`
  - `effEitherInWith`, `effEitherIn`, `effEitherSystemException`
  - `effMaybeWith`, `effMaybeInWith`
  - `pureMaybeInWith`, `pureEitherInWith`
  - `baseEitherIn`, `baseEitherInWith`, `baseMaybeInWith`

### IO lifting and exception bridging

These functions help you bridge `IO` exceptions into algebraic errors:

- `liftIOException :: MonadIO m => IO a -> EffT' c mods '[IOException] m a`
- `liftIOAt       :: (Exception e, MonadIO m) => IO a -> EffT' c mods '[e] m a`
- `liftIOSafeWith :: (Exception e', MonadIO m) => (e' -> e) -> IO a -> EffT' c mods '[e] m a`
- `liftIOText     :: MonadIO m => (Text -> Text) -> IO a -> EffT' c mods '[ErrorText s] m a`
- `liftIOPrepend  :: Text -> IO a -> EffT' c mods '[ErrorText s] IO a`

Try/catch style:

- `effTry`, `effTryWith` - catch exceptions thrown in the *base monad* and turn them into algebraic errors
- `effTryIO`, `effTryIOWith`, `effTryIOIn`, `effTryIOInWith`
- `effTryUncaught` - catch uncaught exceptions into error lists
- `tryAndThrow`, `tryAndThrowWith`, `tryAndThrowText` - lift `IO` and rethrow via `MonadExcept`

### Modules and module helpers

- Core type classes:
  - `Module` - defines `ModuleRead` and `ModuleState` associated data families
  - `SystemModule` - extends `Module` with `ModuleEvent` and `ModuleInitData`
  - `Loadable c mod mods es` - provides `withModule` for scoped module initialisation
- System-wide aliases:
  - `SystemRead c mods`, `SystemState c mods`
  - `SystemEvent mods`, `SystemInitData c mods`
  - `SystemError`
- Accessors from `Control.Monad.Effect`:
  - `queryModule`, `queriesModule`, `askModule`, `asksModule`
  - `getModule`, `getsModule`, `putModule`, `modifyModule`

`withModule` (from `Control.System`) is particularly useful for implementing custom `withX` helpers that allocate resources, push a module on the stack, run an `EffT` computation, and then clean up.

### Bracket patterns and concurrency

Resource-safe patterns:

- `maskEffT` - `mask` in the base monad while staying in `EffT'`
- `generalBracketEffT`, `generalBracketEffT'`
- `bracketEffT`, `bracketEffT'`
- `bracketOnErrorEffT`, `bracketOnErrorEffT'`

Concurrency:

- `forkEffT` - fork an `EffT` computation onto a new thread
- `forkEffTFinally` - variants with finalisers
- `asyncEffT`, `withAsyncEffT`, `withAsyncEffT'` - integrate `async` with `EffT`
- `restoreAsync`, `restoreAsync_` - restore an `EffT` computation from an `Async` result

### RS modules and interfaces

From `Module.RS`:

- Types:
  - `RModule r`, `RNamed name r`
  - `SModule s`, `SNamed name s`
- Running modules:
  - `runRModule`, `runRModuleIn`
  - `runSModule`, `runSModule_`, `runSModuleIn`
- Embedding existing `ReaderT`/`StateT`:
  - `liftReaderT`, `embedReaderT`, `addReaderT`, `asReaderT`
  - `liftStateT`, `embedStateT`, `addStateT`, `asStateT`
- Convenience:
  - `askR`, `asksR`, `localR`
  - `getS`, `getsS`, `putS`, `modifyS`
  - `readOnly` - treat a state module as a read-only module inside a scope

From `Control.Monad.RS.Class`:

- `MonadReadOnly r m`, `MonadReadable r m`, `MonadStateful s m` - reader/state-like APIs without functional dependencies; `EffT'` has instances for these.

From `Control.Monad.Class.Except`:

- `MonadExcept e m` - multiple error types per monad
- `ErrorText`, `ErrorValue`, `errorText`, `errorValue`

### Template Haskell utilities

From `Module.RS.QQ`:

- `makeRModule`, `makeRModule_`, `makeRModule__`
- `makeRSModule`, `makeRSModule_`

`makeRModule` example (simplified):

```haskell
[makeRModule|MyModule
  myRecord1 :: !MyType1
  myRecord2 ::  MyType2
|]
```

Generates (conceptually):

- `data MyModule`
- `instance Module MyModule` with:
  - `data ModuleRead  MyModule  = MyModuleRead { myRecord1 :: !MyType1, myRecord2 :: MyType2 }`
  - `data ModuleState MyModule  = MyModuleState` (plus derivations)
- `instance SystemModule MyModule` with:
  - `data ModuleEvent    MyModule`
  - `data ModuleInitData MyModule`
- runners `runMyModule`, `runMyModuleIn`, etc., and convenient type synonyms for `ModuleRead` / `ModuleState`.

`makeRSModule` similarly builds a combined reader/state module from a compact specification, including optional lens generation for fields tagged with `Lens`.

---

## Performance, style and benchmarks

The core `EffT` design is **very optimisation-friendly**:

- modules are stored in a specialised data family `FData`, not a linked list;
- `FData` is generated by Template Haskell up to a fixed length (e.g. `FData3`, `FData4`, ...) with strict fields; and
- GHC can often optimise `EffT`-based code down to tight loops, competitive with or better than hand-written `StateT`/`ExceptT` stacks.

The benchmarks in `benchmark/` compare:

- `EffT` with `FList` (heterogeneous list),
- `EffT` with `FData`, and
- `StateT` from `mtl`.

On typical countdown/state benchmarks:

- `EffT` + `FData` is around **25 times faster** than `StateT` without optimisation, and
- about as fast as a properly optimised `StateT` (`-O2 -flate-dmd-anal`).

See the SVG charts under `benchmark/bench-result-*` in the repository for details.

### Some Benchmarks

See the `benchmark` folder for more benchmarks. The benchmarks are copied from `heftia`, another effect system library, with some modifications.

#### Countdown `-O2`
![Countdown-O2](https://raw.githubusercontent.com/Eiko-Tokura/monad-effect/3aeceddb0c7e452b34032b404a5fdc068df322de/benchmark/bench-result-O2/countdown-deep.svg)

#### Countdown `-O0`
![Countdown-O0](https://raw.githubusercontent.com/Eiko-Tokura/monad-effect/3aeceddb0c7e452b34032b404a5fdc068df322de/benchmark/bench-result-O0/countdown-deep.svg)

#### Deep Catch `-O2`
![Catch-O2](https://raw.githubusercontent.com/Eiko-Tokura/monad-effect/3aeceddb0c7e452b34032b404a5fdc068df322de/benchmark/bench-result-O2/catch-deep.svg)

#### Deep Catch `-O0`
![Catch-O0](https://raw.githubusercontent.com/Eiko-Tokura/monad-effect/3aeceddb0c7e452b34032b404a5fdc068df322de/benchmark/bench-result-O0/catch-deep.svg)

#### Local State `-O2`
![Local-O2](https://raw.githubusercontent.com/Eiko-Tokura/monad-effect/3aeceddb0c7e452b34032b404a5fdc068df322de/benchmark/bench-result-O2/local-deep.svg)

#### Local State `-O0`
![Local-O0](https://raw.githubusercontent.com/Eiko-Tokura/monad-effect/3aeceddb0c7e452b34032b404a5fdc068df322de/benchmark/bench-result-O0/local-deep.svg)

### Flags

GHC's type-checker sometimes needs more fuel for large module/error lists. It is recommended to build with:

```bash
-fconstraint-solver-iterations=16
```

or slightly higher when using very deep stacks.

### Style and module design

`monad-effect` does not impose a particular way to structure your modules. You can:

- package a concrete implementation (e.g. Prometheus counter, HTTP manager, database connection pool) directly into a module; or
- use a more “algebraic effects” style, where modules carry *handlers* for an algebraic effect GADT.

An example of the latter is a Prometheus counter module that carries a handler as read-only state

```haskell
{-# LANGUAGE DataKinds, TypeFamilies, RequiredTypeArguments #-}
module Module.Prometheus.Counter where

import Control.Monad.Effect
import System.Metrics.Prometheus.Metric.Counter as C

-- | A prometheus counter module that has a name
data PrometheusCounter (name :: k)

-- | Counter effects written in algebraic effect style
data PrometheusCounterEffect a where
  AddAndSampleCounter :: Int -> PrometheusCounterEffect CounterSample
  AddCounter          :: Int -> PrometheusCounterEffect ()
  IncCounter          ::        PrometheusCounterEffect ()
  SetCounter          :: Int -> PrometheusCounterEffect ()
  SampleCounter       ::        PrometheusCounterEffect CounterSample

-- | The effect handler type for a prometheus counter with given counter name
type PrometheusCounterHandler (name :: k) = forall c mods es m a. (In' c (PrometheusCounter name) mods, MonadIO m) => PrometheusCounterEffect a -> EffT' c mods es m a

-- | The module is declared as a reader module that carries a counter handler
instance Module (PrometheusCounter name) where
  newtype ModuleRead  (PrometheusCounter name) = PrometheusCounterRead { prometheusCounterHandler :: PrometheusCounterHandler name }
  data    ModuleState (PrometheusCounter name) = PrometheusCounterState

-- | Specify / interpret a counter effect with given counter name
runPrometheusCounter
  :: forall name
  -> ( ConsFDataList c (PrometheusCounter name : mods)
     , Monad m
     )
  => PrometheusCounterHandler name -> EffT' c (PrometheusCounter name ': mods) es m a -> EffT' c mods es m a
runPrometheusCounter name handler = runEffTOuter_ (PrometheusCounterRead @_ @name handler) PrometheusCounterState
{-# INLINE runPrometheusCounter #-}

-- | Carry out a counter effect with given counter name
prometheusCounterEffect :: forall name -> (In' c (PrometheusCounter name) mods, MonadIO m) => PrometheusCounterEffect a -> EffT' c mods es m a
prometheusCounterEffect name eff = do
  PrometheusCounterRead handler <- askModule @(PrometheusCounter name)
  handler eff
{-# INLINE prometheusCounterEffect #-}

-- | Use a specific counter to carry out a counter effect
useCounter :: Counter -> PrometheusCounterHandler name
useCounter counter IncCounter              = liftIO $ C.inc counter
useCounter counter (AddCounter n)          = liftIO $ C.add n counter
useCounter counter (SetCounter n)          = liftIO $ C.set n counter
useCounter counter (AddAndSampleCounter n) = liftIO $ C.addAndSample n counter
useCounter counter SampleCounter           = liftIO $ C.sample counter
{-# INLINE useCounter #-}

-- | A counter handler that does nothing
noCounter :: Monad m => PrometheusCounterEffect a -> EffT mods es m a
noCounter IncCounter              = pure ()
noCounter (AddCounter _)          = pure ()
noCounter (SetCounter _)          = pure ()
noCounter (AddAndSampleCounter _) = pure (CounterSample 0)
noCounter SampleCounter           = pure (CounterSample 0)
{-# INLINE noCounter #-}
```

---

## Documentation changes from previous versions

Compared to earlier versions of the README:

- The formal definition of `Result` has been updated to match the code in `Data.Result` (single `RSuccess` constructor, with `Result '[] a` behaving like `a`).

- All examples now use the `errorText` / `errorValue` smart constructors instead of directly constructing using constructors `ErrorText` / `ErrorValue`. This avoids the filling kind type parameter `ErrorText @_ @"..."` noise.

- The `myLookup` / `computeAverageFromFile` example has been synchronised with the code in `test/Examples.hs` and corrected to use `errorText @"Map.keyNotFound"` with a proper `Text` value.

- Experimental system orchestration types (`WithSystem`, `EventLoopSystem`, event loops) and the `Resource` module are intentionally **not** described in detail here, as they may change or be removed in future versions. The focus is on `EffT`, modules, algebraic errors, and the RS/Except helper classes.

- Minor wording updates were made throughout to reflect that `EffT`/`Eff` (with `FData`) are the recommended entry points for most users.

For full API details, please refer to the Haddock documentation generated from the source.