Skuld.Effects.Bracket (skuld v0.1.26)

View Source

Bracket 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 resource
  • release_fn - Function (resource -> computation) that releases the resource
  • use_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

  • bracket/3 - Full bracket with acquire, release, and use
  • bracket_/2 - Simplified bracket when acquire returns the resource directly
  • finally/2 - Just ensure cleanup runs (no resource passing)

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

bracket(acquire, release_fn, use_fn)

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 resource
  • release_fn - Function (resource -> computation) that releases the resource
  • use_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
)

bracket_(resource, release_fn, use_fn)

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
)

finally(comp, cleanup)

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
)