Trampoline
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):
- return — value -> 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 bSelectively 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 != tag2run
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, haltThis 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.