Linear
Graded linear resource tracking: acquire/consume/release with usage enforcement.
Each resource gets a capability token at acquire time. The graded handler covers linear (exactly once), affine (at most once via release), exact(n), and unlimited usage through a single maxUses parameter.
Quick start:
let comp = bind (linear.acquireLinear "secret") (token:
bind (linear.consume token) (val:
pure val));
in linear.run comp
For composition with other handlers, use handler/return/initialState with
adaptHandlers.
acquire
Acquire a graded linear resource. Returns a capability token.
acquire : { resource : a, maxUses : Int | null } -> Computation Token
The token wraps the resource with an ID for tracking. The handler maintains a resource map in its state, counting each consume call against the maxUses bound.
maxUses = 1— Linear: exactly one consume requiredmaxUses = n— Exact: exactly n consumes requiredmaxUses = null— Unlimited: any number of consumes allowed
Tokens should be consumed exactly maxUses times, or explicitly
released. At handler exit, the return clause (finalizer) checks:
released → always OK, maxUses = null → always OK,
otherwise → currentUses must equal maxUses.
acquireExact
Acquire a resource that must be consumed exactly n times.
acquireExact : a -> Int -> Computation Token
acquireLinear
Acquire a linear resource (exactly one consume required).
acquireLinear : a -> Computation Token
acquireUnlimited
Acquire an unlimited resource (any number of consumes allowed).
acquireUnlimited : a -> Computation Token
consume
Consume a capability token, returning the wrapped resource value.
consume : Token -> Computation a
Increments the token's usage counter. Aborts with LinearityError if:
- Token was already released ("consume-after-release")
- Usage would exceed maxUses bound ("exceeded-bound")
The returned value is the original resource passed to acquire.
handler
Graded linear resource handler. Interprets linearAcquire, linearConsume, and linearRelease effects. Tracks resource usage in handler state.
Use with trampoline.handle:
handle {
handlers = linear.handler;
return = linear.return;
state = linear.initialState;
} comp
Or use the convenience: linear.run comp
linearAcquire: creates token, adds resource entry to statelinearConsume: increments usage counter, returns resource valuelinearRelease: marks resource as released (finalizer skips it)
initialState
Initial handler state for the linear resource handler.
{ nextId = 0; resources = {}; }
nextId: monotonic counter for generating unique resource IDs.resources: map from ID (string) to resource tracking entry.
release
Explicitly release a capability token without consuming it.
release : Token -> Computation null
Marks the resource as released. The finalizer skips released resources,
so this allows affine usage (acquire then drop). Aborts with
LinearityError on double-release.
return
Finalizer return clause for the linear handler.
Checks each resource in handler state:
- released → OK (explicitly dropped)
- maxUses = null → OK (unlimited)
- otherwise → currentUses must equal maxUses
On violation, wraps the original value in a LinearityError with
details of each mismatched resource. On success, passes through
unchanged. Runs on both normal return and abort paths.
run
Run a computation with the graded linear handler.
run : Computation a -> { value : a | LinearityError, state : State }
Bundles handler, return clause, and initial state into one call.
To compose with other handlers, use handler/return/initialState
separately with adaptHandlers.
let
comp = bind (acquireLinear "secret") (token:
bind (consume token) (val:
pure "got:${val}"));
in linear.run comp
# => { value = "got:secret"; state = { nextId = 1; resources = { ... }; }; }