# Skuld
[](https://github.com/mccraigmccraig/skuld/actions/workflows/test.yml)
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, coroutines via Yield, and composable effect stacks.
## Features
- **Evidence-passing style**: Handlers are looked up directly from the environment, avoiding pattern matching on effect signatures
- **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
- **No Freer/Hefty split**: Single unified `comp` macro for all effects
## Installation
Add `skuld` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:skuld, "~> 0.1.0"}
]
end
```
## Quick Start
```elixir
import Skuld.Syntax
alias Skuld.Comp
alias Skuld.Effects.{State, Reader, Writer, Throw, Yield}
# Define a computation using the comp macro
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}")
return({config, count})
end
# Run with handlers installed
{result, _env} =
example()
|> Reader.with_handler(:my_config)
|> State.with_handler(0)
|> Writer.with_handler()
|> Comp.run()
```
## Effects
### State
Mutable state within a computation:
```elixir
defcomp counter() do
n <- State.get()
_ <- State.put(n + 1)
return(n)
end
{result, _} =
counter()
|> State.with_handler(0)
|> Comp.run()
# result = 0
```
### Reader
Read-only environment:
```elixir
defcomp greet() do
name <- Reader.ask()
return("Hello, #{name}!")
end
{result, _} =
greet()
|> Reader.with_handler("World")
|> Comp.run()
# result = "Hello, World!"
```
### Writer
Accumulating output:
```elixir
defcomp logging() do
_ <- Writer.tell("step 1")
_ <- Writer.tell("step 2")
return(:done)
end
{{result, log}, _} =
logging()
|> Writer.with_handler()
|> Comp.run()
# result = :done
# log = ["step 1", "step 2"]
```
### Throw
Error handling:
```elixir
defcomp might_fail(x) do
if x < 0 do
Throw.throw({:error, "negative value"})
else
return(x * 2)
end
end
defcomp with_recovery() do
result <- Throw.catch_error(
might_fail(-1),
fn error -> return({:recovered, error}) end
)
return(result)
end
{result, _} = with_recovery() |> Comp.run()
# result = {:recovered, {:error, "negative value"}}
```
### Yield
Coroutine-style suspension and resumption:
```elixir
defcomp generator() do
_ <- Yield.yield(1)
_ <- Yield.yield(2)
_ <- Yield.yield(3)
return(:done)
end
# Collect all yielded values
{:done, result, yields, _} =
generator()
|> Yield.with_handler()
|> Yield.collect()
# result = :done
# yields = [1, 2, 3]
# Or drive with a custom function
{:done, _, _} =
generator()
|> Yield.with_handler()
|> Yield.run_with_driver(fn yielded ->
IO.puts("Got: #{yielded}")
{:continue, :ok}
end)
```
### FxList
Effectful list operations:
```elixir
defcomp process_all(items) do
results <- FxList.fx_map(items, fn item ->
comp do
count <- State.get()
_ <- State.put(count + 1)
return(item * 2)
end
end)
return(results)
end
{result, _} =
process_all([1, 2, 3])
|> State.with_handler(0)
|> Comp.run()
# result = [2, 4, 6]
```
> **Note**: For large iteration counts (10,000+), use `Yield`-based coroutines instead
> of `FxList` for better performance. See the FxList module docs for details.
## Architecture
Skuld uses evidence-passing style where:
1. **Handlers** are stored in the environment as functions
2. **Effects** look up their handler and call it directly
3. **CPS** enables control effects (Yield, Throw) to manipulate continuations
4. **Scoped handlers** automatically manage handler installation/cleanup
This avoids the performance overhead of pattern matching on effect signatures
and enables efficient composition of multiple effects.
## Comparison with Freyja
Skuld is a cleaner alternative to Freyja:
| Aspect | Freyja | Skuld |
|--------|--------|-------|
| Effect representation | Freer monad + Hefty algebras | Evidence-passing CPS |
| Control effects | Hefty (higher-order) | Direct CPS |
| Handler dispatch | Pattern matching on signatures | Direct map lookup |
| Macro system | `con` + `hefty` | Single `comp` |
## License
MIT License - see [LICENSE](LICENSE) for details.