Skip to content

freer-simple (Haskell) ​

A friendly extensible-effects library built on freer monads, open unions, and fast type-aligned queues. freer-simple was a major step in making extensible effects practical in Haskell before the newer ReaderT-IO and continuation-primop generations.

FieldValue
LanguageHaskell
LicenseBSD-3-Clause
Repositoryfreer-simple GitHub repository
DocumentationHackage
Key AuthorsAlexis King, Oleg Kiselyov (foundational)
EncodingFreer monad + open union + FTCQueue

Overview ​

What It Solves ​

freer-simple solves two long-standing pain points of classic transformer stacks:

  1. Boilerplate from stacking and lifting monad transformers.
  2. Limited modularity when introducing new effect capabilities.

It gives a single Eff monad with a type-level effect list, plus interpreters that can be composed to remove effects one by one.

Design Philosophy ​

The library emphasizes ergonomics and accessibility for extensible effects: represent effects as ordinary GADTs, send operations with send, and interpret with combinators like interpret/reinterpret. Compared to polysemy, it is lower-level and smaller in scope; compared to mtl, it is more modular in effect declaration and interpretation.


Core Abstractions and Types ​

The Eff Monad ​

The core type is:

haskell
data Eff effs a

where effs is a type-level list of available effects.

Internally, the representation uses:

  • Open unions (Data.OpenUnion) for effect requests.
  • Fast type-aligned queue (Data.FTCQueue) for continuations.

This structure avoids the classic free-monad Functor constraint and supports efficient continuation composition.

Membership Constraints ​

Member/Members constraints declare required capabilities:

haskell
Member (State Int) effs => Eff effs Int

The concrete list stays polymorphic, which enables interpreters to be reordered and combined more flexibly than fixed transformer stacks.

Core Primitives ​

Key primitives include:

  • send to emit an effect request.
  • run for pure programs (Eff '[] a -> a).
  • runM for programs ending in one monadic effect (Eff '[m] a -> m a).
  • Handler constructors like handleRelay / handleRelayS.

How Effects Are Declared ​

Effects are defined as GADTs with one constructor per operation:

haskell
data Teletype r where
  ReadTTY  :: Teletype String
  WriteTTY :: String -> Teletype ()

Operations are then exposed as smart constructors using send, manually or via Template Haskell (makeEffect):

haskell
readTTY :: Member Teletype effs => Eff effs String
readTTY = send ReadTTY

Built-in modules provide common effects (Reader, State, Writer, Error, etc.) as reusable GADTs plus handlers.


How Handlers/Interpreters Work ​

Interpreters eliminate effects from the row.

First-Order Interpretation ​

interpret maps each operation to the remaining Eff stack:

haskell
interpret
  :: (forall v. e v -> Eff effs v)
  -> Eff (e ': effs) a
  -> Eff effs a

Reinterpretation ​

reinterpret converts one effect into another (or several), enabling layered implementation without exposing private internal effects in external signatures.

Running ​

Programs typically compose several handlers then finish with run or runM.

This gives a clear "build syntax first, interpret later" workflow similar to other extensible-effect systems.


Performance Approach ​

Why It Was a Breakthrough ​

freer-simple improved significantly over older free encodings by using freer representation plus open-union dispatch and FTCQueue continuations. For many teams, it made extensible effects viable without custom compiler support.

Current Performance Position ​

Relative to newer libraries, freer-simple is generally slower in deep effect-heavy workloads because it still builds/interprets effect requests at runtime. Benchmarks in later ecosystem discussions (and in effectful benchmark suite comparisons) consistently place it behind effectful, cleff, and usually fused-effects.

The key takeaway: historically important and still expressive, but no longer state-of-the-art on throughput/latency-sensitive hot paths.


Composability Model ​

Strength of the Model ​

  • Effects are declared as independent algebras.
  • Programs can stay polymorphic over effs.
  • Interpreters can be swapped for pure tests or IO-backed execution.

Limits ​

  • Compared to polysemy, higher-order/scoped effects are less ergonomic and less central to the core design.
  • Compared to ReaderT-IO systems (effectful, cleff), interoperability with mainstream MonadUnliftIO-style ecosystems is less direct.

Strengths ​

  • Historically foundational: key bridge from transformers to modern extensible effects.
  • Good ergonomics for first-order effects: concise GADT + send + interpreter workflow.
  • Pure interpretation support: easy to test and reason about interpreter logic.
  • Modular effect rows: avoids many stack-coupling issues of monad transformers.
  • Template Haskell helpers: makeEffect reduces operation boilerplate.

Weaknesses ​

  • Performance lag vs newer systems: usually behind effectful/cleff/fused-effects on demanding benchmarks.
  • Aging ecosystem momentum: fewer recent releases and less active expansion than newer libraries.
  • Higher-order effect ergonomics: weaker than later designs centered around HO effects.
  • Complex internals: open-union + continuation machinery is non-trivial for contributors.

Key Design Decisions and Trade-offs ​

DecisionRationaleTrade-off
Freer-monad coreExtensible effects as data, modular interpretersRuntime interpretation overhead
Open union dispatchType-safe membership of many effectsInternal complexity
FTCQueue continuationsBetter asymptotic continuation compositionHarder internals than plain transformers
Library-level implementationNo compiler patching requiredLimited runtime optimization opportunities
TH (makeEffect) supportLower user boilerplateTH dependency and generated-code opacity

Sources ​