Skuld.Effects.Throw (skuld v0.1.26)
View SourceThrow/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)returns%Throw{error: error}as the resultcatch_errorinstalls a leave_scope that intercepts%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
Summary
Functions
Install Throw handler via catch clause syntax.
Catch errors from a sub-computation.
Default handler - return Throw struct as result (does not call k)
Intercept thrown errors locally within a computation.
Throw an error - does not resume
Catch and return Either-style result.
Install a scoped Throw handler for a computation.
Functions
Install Throw handler via catch clause syntax.
Config is ignored (Throw handler takes no configuration):
catch
Throw -> nil
@spec catch_error(Skuld.Comp.Types.computation(), (term() -> 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 -> Comp.pure(: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
)
Default handler - return Throw struct as result (does not call k)
@spec intercept(Skuld.Comp.Types.computation(), (term() -> 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.
@spec throw(term()) :: Skuld.Comp.Types.computation()
Throw an error - does not resume
@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
IThrowableto return cleaner error values
For other exception kinds:
:throwvalues become{:error, {:thrown, value}}:exitreasons 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)
endIThrowable 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
@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()
return(result)
end
|> Throw.with_handler()
# Compose with other handlers
my_comp
|> Throw.with_handler()
|> State.with_handler(0)
|> Comp.run(Env.new())