Test Hex.pm Documentation

Evidence-passing Algebraic Effects for Elixir.

Skuld is a clean, efficient implementation of Algebraic Effects using evidence-passing style with CPS (continuation-passing style) for control effects. It provides scoped handlers, composable effect stacks, and a library of useful effects.

Algebraic effects add an architectural layer between pure and side-effecting code: instead of just pure functions and side-effecting functions, you have pure functions, effectful functions, and side-effecting handlers. Domain code is written with effects but remains pure - the same code runs with test handlers (pure, in-memory) or production handlers (real I/O). This enables clean separation of concerns, property-based testing, and serializable coroutines for durable computation.

Skuld's library of effects aims to provide primitives broad enough that most domain computations can use effectful operations instead of side-effecting ones. Here are some common side-effecting operations and their effectful equivalents:

Side-effecting operationEffectful equivalent
Configuration / environmentReader
Process dictionaryState, Writer
Random valuesRandom
Generating IDs (UUIDs)Fresh
Concurrent fibers / streamingFiberPool, Channel, Brook
Run effects from LiveViewAsyncComputation
DB writes & transactionsDB
Typed batchable queriesquery, deffetch, Query.Cache
Blocking calls to external codePort, Port.Contract
Effectful code from plain codePort.Provider
Decider patternCommand, EventAccumulator
Serializable coroutinesEffectLogger
Raising exceptionsThrow
Resource cleanup (try/finally)Bracket
Control flowYield
Lists of effectful computationsFxList, FxFasterList

Documentation

Features

  • Evidence-passing style: Handlers are looked up directly from a map in the dynamic environment
  • CPS for control effects: Enables proper support for control flow effects like Yield and Throw
  • Scoped handlers: Handlers are automatically installed/restored with proper cleanup
  • Composable: Multiple effects can be stacked and composed naturally
  • Single type: Single unified computation type and comp macro for all effectful code - ideal for dynamic languages
  • Auto-lifting: Plain values are automatically lifted to computations, enabling ergonomic patterns like if without else and implicit final returns

Installation

Add skuld to your list of dependencies in mix.exs (see the Hex package for the current version):

def deps do
  [
    {:skuld, "~> x.y"}
  ]
end

Demo Application

See TodosMcp - a voice-controllable todo application built with Skuld. It demonstrates how command/query structs combined with algebraic effects enable trivial LLM integration and property-based testing. Try it live at https://todos-mcp-lu6h.onrender.com/

Quick Start

use Skuld.Syntax
alias Skuld.Comp
alias Skuld.Effects.{State, Reader, Writer, Throw, Yield}

# Define a computation using the comp macro
defmodule Example do
  defcomp example() do
    # Read from Reader effect
    config <- Reader.ask()

    # Get and update State
    count <- State.get()
    _ <- State.put(count + 1)

    # Write to Writer effect
    _ <- Writer.tell("processed item #{count}")

    {config, count}  # final expression auto-lifted (no return needed)
  end
end

# Run with handlers installed
Example.example()
  |> Reader.with_handler(:my_config)
  |> State.with_handler(0, output: fn r, st -> {r, {:final_state, st}} end)
  |> Writer.with_handler([], output: fn r, w -> {r, {:log, w}} end)
  |> Comp.run!()

#=> {{{:my_config, 0}, {:final_state, 1}}, {:log, ["processed item 0"]}}

See the Syntax guide for full details on the comp block, pattern matching, error handling, and more.

License

MIT License - see LICENSE for details.