Skuld.Effects.Bracket (skuld v0.1.26)
View SourceBracket effect for safe resource acquisition and cleanup.
Bracket ensures that resources are properly released even when errors occur. This is essential for managing resources like file handles, database connections, locks, and network connections.
Overview
The bracket/3 function takes three arguments:
acquire- Computation that acquires a resourcerelease_fn- Function(resource -> computation)that releases the resourceuse_fn- Function(resource -> computation)that uses the resource
The release function is guaranteed to run exactly once, whether the use computation succeeds or throws an error.
Example
import Skuld.Syntax
result <- Bracket.bracket(
# Acquire resource
comp do
handle <- open_file("data.txt")
return(handle)
end,
# Release (always runs)
fn handle ->
comp do
_ <- close_file(handle)
return(:ok)
end
end,
# Use resource
fn handle ->
comp do
content <- read_file(handle)
return(process(content))
end
end
)Error Handling
If the use computation throws an error, the release function runs before the error is re-thrown:
Bracket.bracket(
acquire_connection(),
fn conn -> release_connection(conn) end,
fn conn ->
comp do
# If this throws, conn is still released
result <- dangerous_operation(conn)
return(result)
end
end
)If the release function itself throws:
- If the use computation succeeded, the release error propagates
- If the use computation threw, the original error propagates (release error is suppressed)
Nested Brackets
Brackets can be nested. Each bracket manages its own resource independently:
Bracket.bracket(
acquire_outer(),
fn outer -> release_outer(outer) end,
fn outer ->
comp do
inner_result <- Bracket.bracket(
acquire_inner(),
fn inner -> release_inner(inner) end,
fn inner -> use_both(outer, inner) end
)
return(inner_result)
end
end
)Resources are released in LIFO order (inner first, then outer).
Convenience Functions
Summary
Functions
Acquire a resource, use it, and ensure it is released.
Simplified bracket when acquire computation directly returns the resource.
Ensure cleanup runs after a computation, regardless of success or failure.
Functions
@spec bracket( Skuld.Comp.Types.computation(), (term() -> Skuld.Comp.Types.computation()), (term() -> Skuld.Comp.Types.computation()) ) :: Skuld.Comp.Types.computation()
Acquire a resource, use it, and ensure it is released.
The release function is guaranteed to run exactly once, whether the use computation succeeds or throws an error.
Parameters
acquire- Computation that acquires a resourcerelease_fn- Function(resource -> computation)that releases the resourceuse_fn- Function(resource -> computation)that uses the resource
Returns
A computation that returns the result of the use function.
Example
Bracket.bracket(
comp do
Logger.info("Acquiring lock")
lock <- Lock.acquire(:my_lock)
return(lock)
end,
fn lock ->
comp do
Logger.info("Releasing lock")
_ <- Lock.release(lock)
return(:ok)
end
end,
fn lock ->
comp do
# Critical section - lock is held
result <- do_work()
return(result)
end
end
)
@spec bracket_(term(), (term() -> Skuld.Comp.Types.computation()), (term() -> Skuld.Comp.Types.computation())) :: Skuld.Comp.Types.computation()
Simplified bracket when acquire computation directly returns the resource.
This is a convenience wrapper when you don't need a separate acquire computation.
Example
# Instead of:
Bracket.bracket(
Comp.pure(file_handle),
fn h -> close(h) end,
fn h -> read(h) end
)
# You can write:
Bracket.bracket_(
file_handle,
fn h -> close(h) end,
fn h -> read(h) end
)
@spec finally(Skuld.Comp.Types.computation(), Skuld.Comp.Types.computation()) :: Skuld.Comp.Types.computation()
Ensure cleanup runs after a computation, regardless of success or failure.
This is like bracket but without resource passing - just ensures the
cleanup computation runs.
Example
Bracket.finally(
comp do
_ <- Logger.info("Starting operation")
result <- do_work()
return(result)
end,
comp do
_ <- Logger.info("Operation complete")
return(:ok)
end
)