Why Effects? >

Test Hex.pm Documentation

An effectful programming framework for Elixir.

                          Comp
                     (lazy computation,
                      evidence-passing,
                      scoped handlers)
                            
      
                                                      
 //Foundational       //Coroutines &               //Boundaries
 //Effects            //Concurrency                     
                                                      
                       Coroutine                
                                                         
 State, Reader,                      Port
 Writer, Throw,                                       Port.EffectfulFacade
 Bracket, Fresh,     Serializable-                     Repo
 Random, FxList,      Coroutine                      
 Yield,                                              
 EffectLogger,  AsyncCoroutine     FiberPool       Adapter
 Parallel,                                        Adapter.EffectfulContract
 AtomicState,                         
 Transaction,                                  
 Command                                       
                                     
                              Channel    Task   
                                               
                               Brook            
                                                
                                           Query.Contract
                                           QueryBlock
                                           (Haxl-like: auto-batches fetches
                                           via Coroutine fibers)

The old problem

Between pure business logic and side-effecting infrastructure sits the orchestration layer — "fetch the user, check permissions, load their subscription, hit some APIs, compute a price, write an invoice." This code encodes your most important business rules, but it's tangled with databases, APIs, and randomness — making it hard to test, hard to refactor, and often — impossible to property-test.

Another way

Skuld lets you write orchestration code that describes side effects without performing them — then handlers decide what those descriptions mean. The exact same "effectful" code runs with side-effecting handlers in production and pure in-memory handlers in tests — fully deterministic, fully pure, and straightforwardly property-testable.

Because effects are first-class data, Skuld can do more — batch independent queries automatically, serialise partially complete computations for later resumption.

Quick example

A multi-step checkout wizard. The computation pauses at each step, waiting for external input — then resumes with full effect context preserved:

defmodule Checkout do
  use Skuld.Syntax

  alias Skuld.Effects.Yield

  defcomp run do
    cart <- Yield.yield(:get_cart)
    {:ok, inventory} <- Inventory.check_stock(cart.items)
    payment <- Yield.yield(:get_payment)
    {:ok, order} <- Orders.place(cart, payment)
    _ <- Emailer.send_confirmation(order)
    {:ok, order}
  end
end

Run it from a LiveView with AsyncCoroutine:

# mount
{:ok, runner} = AsyncCoroutine.run(Checkout.run(), tag: :checkout)

# handle_info — the wizard pauses at each yield
def handle_info({AsyncCoroutine, :checkout, %ExternalSuspend{value: :get_cart}}, socket) do
  cart = ShoppingCart.get_cart(socket.assigns.user)
  AsyncCoroutine.run(socket.assigns.runner, cart)   # resume with cart
  {:noreply, socket}
end

def handle_info({AsyncCoroutine, :checkout, %ExternalSuspend{value: :get_payment}}, socket) do
  payment = socket.assigns.payment_form |> to_payment_method()
  AsyncCoroutine.run(socket.assigns.runner, payment) # resume with payment
  {:noreply, socket}
end

def handle_info({AsyncCoroutine, :checkout, {:ok, order}}, socket) do
  {:noreply, assign(socket, order: order, step: :done)}
end

Test it — drive the whole wizard in a few lines:

comp =
  Checkout.run()
  |> Port.with_handler(%{Inventory => Inventory.Test, Orders => Orders.Test})
  |> Port.with_test_handler(%{Port.key(Emailer, :send_confirmation, [_]) => :ok})
  |> Yield.with_handler()
  |> Throw.with_handler()

fiber = comp |> Coroutine.new(Env.new()) |> Coroutine.run()    # pauses at :get_cart
fiber = Coroutine.run(fiber, %{items: [...]})                  # pauses at :get_payment
%Coroutine.Completed{result: {:ok, order}} =
  Coroutine.run(fiber, %{card: "4242..."})                     # completes

Same code. Production pauses at each step for user input. Tests drive the entire wizard in a single function — deterministic, no processes, no stubs.

Composability

Effects compose with zero ceremony. This streaming pipeline processes a sequence of commands through a pure decider, persists events via an automatically batched deffetch contract (eliminating N+1 queries!), and pushes concurrency and backpressure to the handler layer:

def process_stream(stream) do
  stream
  |> Brook.flat_map(fn cmd ->
    comp do
      state <- State.get()
      BankAccount.decide(cmd, state)
    end
  end)
  |> Brook.map(&EventStore.evolve/1, concurrency: 4)
end

# Wire everything up — batching, concurrency, error handling, persistence
commands
|> Brook.from_enum()
|> process_stream()
|> Brook.to_list()
|> Skuld.Query.with_executor(EventStore, EventStore.EctoExecutor)
|> State.with_handler(%{balance: 0})
|> Channel.with_handler()
|> FiberPool.with_handler()
|> Throw.with_handler()
|> Comp.run!()

process_stream knows nothing about databases, batch sizes, or concurrency limits. Two lines of business logic. Everything else is handler wiring — swappable, testable, composable.

Full decider pattern →

Installation

def deps do
  [
    {:skuld, "~> 0.27"}
  ]
end

Where next?

If you want to...Read
Understand the problem effects solveWhy Effects?
See how effects and handlers workHow It Works
Write your first computationGetting Started
State, Reader, Writer, Throw, Fresh, RandomFoundational Effects
Yield, Coroutines, FiberPool, Channels, AsyncCoroutines & Concurrency
Port, Repo, Hexagonal ArchitectureBoundaries
Eliminate N+1 queriesQuery System
Handler-swapping for deterministic testingTesting
Full effect and API referenceReference

License

MIT License — see LICENSE for details.


Why Effects? >