# `Skuld.Effects.Yield`
[🔗](https://github.com/mccraigmccraig/skuld/blob/main/lib/skuld/effects/yield.ex#L1)

Yield effect - coroutine-style suspension and resumption.

Uses `%Skuld.Comp.ExternalSuspend{}` struct as the suspension result, which
bypasses leave_scope in Run.

## Architecture

- `yield(value)` emits `Comp.effect(sig(), {Yield.Yield, value})`
- The handler converts this to `%ExternalSuspend{value, resume}`
- The resume function captures the env, so caller just provides input
- Run recognizes `%ExternalSuspend{}` and bypasses leave_scope
- When resumed, the result goes through the leave_scope chain

# `__handle__`

Install Yield handler via catch clause syntax.

Config is ignored (Yield handler takes no configuration):

    catch
      Yield -> nil

# `collect`

```elixir
@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`.

# `feed`

```elixir
@spec feed(Skuld.Comp.Types.computation(), [term()]) ::
  {:done, term(), [term()], Skuld.Comp.Types.env()}
  | {:suspended, term(),
     (term() -&gt; {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`.

# `handle`

Handler: returns Suspend struct with resume that captures env
and invokes leave_scope when the resumed computation completes.

# `intercept`

```elixir
@spec intercept(Skuld.Comp.Types.computation(), (term() -&gt;
                                             Skuld.Comp.Types.computation())) ::
  Skuld.Comp.Types.computation()
```

Intercept yields locally within a computation.

This is the `IIntercept.intercept/2` implementation for Yield, enabling
`{Yield, pattern}` clauses in `comp` block `catch` sections.

Delegates to `respond/2`.

# `respond`

```elixir
@spec respond(Skuld.Comp.Types.computation(), (term() -&gt;
                                           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 -> 10
          :get_another -> 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 -> 10
          other -> Yield.yield(other)  # re-yield to outer handler
        end
      )
      result
    end
    |> Yield.with_handler()
    |> Comp.run()
    # Returns %Suspend{value: :not_handled, ...}

# `run_with_driver`

```elixir
@spec run_with_driver(
  Skuld.Comp.Types.computation(),
  (value :: term(), data :: map() | nil -&gt;
     {:continue, term()} | {:cancel, term()})
) ::
  {:done, term(), Skuld.Comp.Types.env()}
  | {:cancelled, Skuld.Comp.Cancelled.t(), 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}` - resume the computation with input
- `{:cancel, reason}` - cancel the computation, invoking leave_scope for cleanup

The computation should already have handlers installed via `with_handler`.

## Returns

- `{:done, value, env}` - computation completed successfully
- `{:cancelled, %Cancelled{}, env}` - computation was cancelled with cleanup
- `{:thrown, error, env}` - computation threw an error

# `state_key`

# `with_handler`

```elixir
@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)
        {:got, input}
      end
      |> Yield.with_handler()

    # Compose with other handlers
    my_comp
    |> Yield.with_handler()
    |> State.with_handler(0)
    |> Comp.run(Env.new())

# `yield`

```elixir
@spec yield() :: Skuld.Comp.Types.computation()
```

Yield without a value

---

*Consult [api-reference.md](api-reference.md) for complete listing*
