Navigation
/
Copy page

Trampoline

On this page
On this page

Trampolined interpreter using builtins.genericClosure for O(1) stack depth.

handle

handle: trampolined handler combinator with a custom return clause; interprets the computation through handlers from initial state then folds the pair via return.

handle : { return ? Identity, handlers, state ? null } -> Computation a -> { value, state }

Trampolined handler combinator with return clause.

Follows Kiselyov & Ishii's handle_relay pattern but trampolined via genericClosure for O(1) stack depth.

Arguments (attrset): - returnvalue -> state -> { value, state }. How to transform the final Pure value. Default: identity. - handlers{ effectName = { param, state }: { resume | abort, state }; }. Each must return { resume; state; } or { abort; state; }. - state — initial handler state. Default: null.

Handler state and closure-valued fields — the trampoline deepSeq-forces handler state at each step. Derivations and other values that hang under deepSeq must be wrapped with fx.state.thunk.mkThunk before being stored in handler state, and unwrapped with fx.state.thunk.forceThunk after handle returns. Closure-valued fields are opaque to deepSeq and don't need wrapping, but any attrset field reachable from state that contains a derivation or pointer-cyclic value does.

rotate

rotate: selectively handle known effects and rotate unknown ones outward; matches the Kyo-style handler-rotation law for nested scopes.

rotate : { return ? Identity, handlers, state ? null } -> Computation a -> Computation b

Selectively handle known effects and rotate unknown effects outward.

If the current effect has a matching handler, the handler is applied. If it does not match, the effect is re-suspended and its continuation is wrapped so handling resumes after that effect is interpreted by an outer handler.

This corresponds to the Kyo-style handler rotation law from https://gist.github.com/vic/3a7f52974a28675dbaf40b34bec74787:

handle(tag1, suspend(tag2, i, k), f) = suspend(tag2, i, x => handle(tag1, k(x), f))` for `tag1 != tag2

run

run: drive a computation through the genericClosure trampoline with a handler attrset and initial state; returns { value, state } at O(1) stack depth.

run : Computation a -> Handlers -> State -> { value : a, state : State }

Run a computation through the genericClosure trampoline.

Arguments: - comp — the freer monad computation to interpret - handlers{ effectName = { param, state }: { resume | abort, state }; ... } - initialState — starting state passed to handlers

Handlers must return one of:

{ resume = value; state = newState; }  -- invoke continuation with value
{ abort  = value; state = newState; }  -- discard continuation, halt

This is the defunctionalized encoding of Plotkin & Pretnar (2009): resume ≡ invoke continuation k(v), abort ≡ discard k.

Stack depth: O(1) — constant regardless of computation length. Time: O(n) where n = number of effects in the computation.

Handler state and closure-valued fields — the trampoline deepSeq-forces handler state at each step. Derivations and other values that hang under deepSeq must be wrapped with fx.state.thunk.mkThunk before being stored in handler state, and unwrapped with fx.state.thunk.forceThunk after run returns. Closure-valued fields are opaque to deepSeq and don't need wrapping, but any attrset field reachable from state that contains a derivation or pointer-cyclic value does.