Skuld.Effects.Yield (skuld v0.1.13)
View SourceYield effect - coroutine-style suspension and resumption.
Uses %Skuld.Comp.Suspend{} struct as the suspension result, which
bypasses leave_scope in Run.
Architecture
yield(value)suspends computation, returning%Suspend{value, resume}- The resume function captures the env, so caller just provides input
- Run recognizes
%Suspend{}and bypasses leave_scope - When resumed, the result goes through the leave_scope chain
Summary
Functions
Collect all yielded values until completion.
Feed a list of inputs to a computation, collecting yields.
Handler: returns Suspend struct with resume that captures env and invokes leave_scope when the resumed computation completes.
Intercept yields from a computation and respond to them.
Run a computation with a driver function that handles yields.
Install a scoped Yield handler for a computation.
Yield without a value
Yield a value and suspend, waiting for input to resume
Functions
@spec collect(Skuld.Comp.Types.computation(), term()) :: {:done, term(), [term()], Skuld.Comp.Types.env()} | {:thrown, term(), [term()], Skuld.Comp.Types.env()}
Collect all yielded values until completion.
Resumes with the provided input value (default: nil) each time.
The computation should already have handlers installed via with_handler.
@spec feed(Skuld.Comp.Types.computation(), [term()]) :: {:done, term(), [term()], Skuld.Comp.Types.env()} | {:suspended, term(), (term() -> {Skuld.Comp.Types.result(), Skuld.Comp.Types.env()}), [term()], Skuld.Comp.Types.env()} | {:thrown, term(), [term()], Skuld.Comp.Types.env()}
Feed a list of inputs to a computation, collecting yields.
Each yield consumes one input. If inputs run out, stops with remaining computation.
The computation should already have handlers installed via with_handler.
Handler: returns Suspend struct with resume that captures env and invokes leave_scope when the resumed computation completes.
@spec respond(Skuld.Comp.Types.computation(), (term() -> Skuld.Comp.Types.computation())) :: Skuld.Comp.Types.computation()
Intercept yields from a computation and respond to them.
Similar to Throw.catch_error/2, but for yields instead of throws. The responder
function receives the yielded value and returns a computation that produces the
resume value. If the responder re-yields (calls Yield.yield), that yield
propagates to the outer handler.
Implementation Note
This implementation wraps the Yield handler in the Env to intercept yields
directly at the handler level. A previous implementation used a different
approach: replacing the leave_scope with an identity function, running the
inner computation to completion, then pattern matching on %Suspend{} results
to detect yields. That approach was more complex (~180 lines vs ~80 lines),
required manual env state merging, and interfered with other effects that
rely on leave_scope (such as EffectLogger).
Example
# Handle all yields internally:
comp do
result <- Yield.respond(
comp do
x <- Yield.yield(:get_value)
y <- Yield.yield(:get_another)
x + y
end,
fn
:get_value -> Comp.pure(10)
:get_another -> Comp.pure(20)
end
)
result
end
|> Yield.with_handler()
|> Comp.run!()
#=> 30
# Responder can use effects:
comp do
result <- Yield.respond(
comp do
x <- Yield.yield(:get_state)
x * 2
end,
fn :get_state -> State.get() end
)
result
end
|> State.with_handler(21)
|> Yield.with_handler()
|> Comp.run!()
#=> 42
# Unhandled yields propagate (re-yield):
comp do
result <- Yield.respond(
comp do
x <- Yield.yield(:handled)
y <- Yield.yield(:not_handled)
x + y
end,
fn
:handled -> Comp.pure(10)
other -> Yield.yield(other) # re-yield to outer handler
end
)
result
end
|> Yield.with_handler()
|> Comp.run()
# Returns %Suspend{value: :not_handled, ...}
@spec run_with_driver( Skuld.Comp.Types.computation(), (value :: term(), data :: map() | nil -> {:continue, term()} | {:stop, term()}) ) :: {:done, term(), Skuld.Comp.Types.env()} | {:stopped, term(), Skuld.Comp.Types.env()} | {:thrown, term(), Skuld.Comp.Types.env()}
Run a computation with a driver function that handles yields.
The driver receives yielded values and returns {:continue, input} or {:stop, reason}.
The computation should already have handlers installed via with_handler.
@spec with_handler(Skuld.Comp.Types.computation()) :: Skuld.Comp.Types.computation()
Install a scoped Yield handler for a computation.
Installs the Yield handler for the duration of comp. The handler is
restored/removed when comp completes or suspends.
The argument order is pipe-friendly.
Example
# Wrap a computation with Yield handling
comp_with_yield =
comp do
input <- Yield.yield(:question)
return({:got, input})
end
|> Yield.with_handler()
# Compose with other handlers
my_comp
|> Yield.with_handler()
|> State.with_handler(0)
|> Comp.run(Env.new())
@spec yield() :: Skuld.Comp.Types.computation()
Yield without a value
@spec yield(term()) :: Skuld.Comp.Types.computation()
Yield a value and suspend, waiting for input to resume