Skuld

TestHex.pmDocumentation

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 operation Effectful equivalent
Configuration / environment Reader
Process dictionary State, Writer
Random values Random
Generating IDs (UUIDs) Fresh
Concurrent fibers / streaming FiberPool, Channel, Brook
Run effects from LiveView AsyncComputation
DB writes & transactions DB
Typed batchable queries query, deffetch, Query.Cache
Blocking calls to external code Port, Port.Contract
Effectful code from plain code Port.Provider
Decider pattern Command, EventAccumulator
Serializable coroutines EffectLogger
Raising exceptions Throw
Resource cleanup (try/finally) Bracket
Control flow Yield
Lists of effectful computations FxList, FxFasterList

Documentation

Features

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.