Trampoline
Trampolined interpreter using builtins.genericClosure for O(1) stack depth.
handle
Trampolined handler combinator with return clause.
handle : { return?, handlers, state? } -> Computation a -> { value, state }
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.
rotate
Selectively handle known effects and rotate unknown effects outward.
rotate : { return?, handlers, state? } -> Computation a -> Computation b
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 a computation through the genericClosure trampoline.
run : Computation a -> Handlers -> State -> { value : a, state : State }
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.