Skip to content
Logo

Resource management with on_exit and Scope

on_exit attaches a finalizer that runs when the effect settles, on success and failure alike. A Scope collects finalizers from a whole sub-tree: acquire_and_release registers a release for an acquired resource, and .scoped() provides the Scope and runs the collected finalizers in reverse order when the wrapped effect settles. It composes with provide chains — program.provide(Db)(db).scoped() — and is a no-op on effects that never acquired a Scope, so it can uniformly terminate a chain.

E.(21).(E.("done"))  # finalizer runs on success and failure alike
 
 = E.(
    E.(lambda: .()),  # acquire
    lambda : E.(.),  # release, guaranteed by the enclosing scope
)  # Effect[Connection, Never, Scope]
 
 = .(
    
).()  # Scope discharged; close() runs when program settles

on_exit also takes a function that receives the Exit and returns the finalizer, so cleanup can depend on how the effect settled:

E.(21).(
    lambda : E.("settled", )
)  # Succeeded(21), or Failure(cause) carrying Fail, Die or Interrupt

A finalizer that dies doesn't skip the remaining finalizers; its defect surfaces in the final Exit.

More examples: test_scope.py.