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

Throw/Catch effects - error handling with scoped catching.

Uses the `%Skuld.Comp.Throw{}` struct as the error result type, which
is intercepted by `catch_error` via leave_scope.

## Architecture

- `throw(error)` emits `Comp.effect(sig(), {Throw.Throw, error})`
- The handler converts this to `%Comp.Throw{error: error}` as the result
- `catch_error` installs a leave_scope that intercepts `%Comp.Throw{}` results
- When caught, the recovery computation runs and continues normal flow
- Normal completion passes through unchanged
- If recovery re-throws, the error propagates to outer catch handlers

# `__handle__`

Install Throw handler via catch clause syntax.

Config is ignored (Throw handler takes no configuration):

    catch
      Throw -> nil

# `catch_error`

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

Catch errors from a sub-computation.

If the sub-computation throws, the error handler is invoked and its result
continues through normal flow (the continuation chain). This allows catch
to fully recover from errors - subsequent binds will receive the recovery value.

If the recovery computation itself throws, that error propagates through
the leave_scope chain to any outer catch handlers.

Normal completion passes through unchanged (no wrapping).

## Example

    # Transparent recovery - catch fully handles the error
    Throw.catch_error(
      risky_computation(),
      fn :not_found -> :default end
    )
    # Returns either the value or :default

    # Nested catch - inner catches first, unhandled propagates to outer
    Throw.catch_error(
      Throw.catch_error(inner, fn :a -> ... end),
      fn :b -> ... end
    )

# `handle`

Default handler - return Throw result (does not call k)

# `intercept`

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

Intercept thrown errors locally within a computation.

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

Delegates to `catch_error/2`.

# `state_key`

# `try_catch`

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

Catch and return Either-style result.

Wraps both success and error paths for uniform handling:
- Success: `{:ok, value}`
- Error: `{:error, error}`

## Exception Handling

When exceptions are raised inside computations, they are caught and converted
to `{:error, unwrapped_value}`. The `Skuld.Comp.IThrowable` protocol determines
how exceptions are unwrapped:

- By default, exceptions are returned as-is (e.g., `{:error, %ArgumentError{}}`)
- Domain exceptions can implement `IThrowable` to return cleaner error values

For other exception kinds:
- `:throw` values become `{:error, {:thrown, value}}`
- `:exit` reasons become `{:error, {:exit, reason}}`

## Example

    result = Throw.try_catch(risky_computation())
    case result do
      {:ok, value} -> handle_success(value)
      {:error, %ArgumentError{}} -> handle_bad_input()
      {:error, {:not_found, id}} -> handle_not_found(id)
    end

## IThrowable Protocol

Implement `Skuld.Comp.IThrowable` for domain exceptions to get clean error values:

    defimpl Skuld.Comp.IThrowable, for: MyApp.NotFoundError do
      def unwrap(%{entity: entity, id: id}), do: {:not_found, entity, id}
    end

# `with_handler`

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

Install a scoped Throw handler for a computation.

Installs the Throw handler for the duration of `comp`. The handler is
restored/removed when `comp` completes or throws.

The argument order is pipe-friendly.

## Example

    # Wrap a computation with Throw handling
    comp_with_throw =
      comp do
        result <- risky_operation()
        result
      end
      |> Throw.with_handler()

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

---

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