Documentation
¶
Overview ¶
Package kont provides continuation-passing style primitives and algebraic effects in Go.
The core type Cont represents a computation that accepts a continuation and produces a final result. This encoding enables delimited control operators such as Shift and Reset for capturing and manipulating continuations.
Design Philosophy ¶
kont provides:
- Minimal but complete interfaces for continuations, control, and effects
- F-bounded polymorphism for compile-time dispatch and devirtualization
- Defunctionalized evaluation with allocation-free evaluation loops (construction may allocate)
F-Bounded Architecture ¶
The package uses Go 1.26 F-bounded polymorphism (type T[P T[P]]) as a core architectural principle. This enables:
- Compile-time knowledge of concrete types at monomorphization time
- Potential devirtualization of dispatch calls by the compiler
- Allocation-free trampoline loops for effect handling through typed dispatch
Key F-bounded interfaces:
- Op: type Op[O Op[O, A], A any] where operations know their concrete type
- Handler: type Handler[H Handler[H, R], R any] where handlers know their concrete type
Core Operations ¶
Minimal monad operations:
Derived operations:
- Map: Apply a function to the result, equivalent to Bind(m, func(a) Return(f(a)))
- Then: Sequence discarding first result, equivalent to Bind(m, func(_) n)
Convenience:
- Eff: Effectful computation type alias for Cont[Resumed, A]
- Pure: Lift a value into Eff with full type inference
Execution:
- Suspend: Create a continuation from a CPS function
- Run: Execute a continuation to obtain the result
- RunWith: Execute with a custom final continuation
Delimited Control ¶
Stepping Boundary ¶
Step and StepExpr provide one-effect-at-a-time evaluation for external runtimes that drive computation asynchronously (e.g., event loops). Unlike Handle/HandleExpr, which run a synchronous trampoline to completion, the stepping API yields control at each effect suspension.
Nil completion convention: effect runners and stepping treat a nil Resumed value as “completed with the zero value”. This implies computations whose final result type is a pointer or interface cannot use nil as a meaningful result value; wrap such results in a sum type (e.g., Either) if you need to distinguish “completed with nil” from “completed with zero”.
- Step: Drive a Cont computation until it completes or suspends
- StepExpr: Drive an Expr computation until it completes or suspends
- Suspension: Pending operation with one-shot resumption handle
- Suspension.Op: Returns the effect operation that caused the suspension
- Suspension.Resume: Advance to the next suspension or completion (panics on reuse)
- Suspension.TryResume: Non-panicking variant of Resume
- Suspension.Discard: Drop without invoking
Returns (value, nil) on completion, or (zero, *Suspension) when pending. Affine semantics: each Suspension may be resumed at most once.
Algebraic Effects ¶
Effects are defined as types implementing the F-bounded Op constraint, and handlers interpret these effects via the F-bounded Handler interface. Handler dispatch returns (resumeValue, true) to continue the computation, or (finalResult, false) to short-circuit.
- Op: F-bounded effect operation interface
- Phantom: Embeddable zero-size Op result marker
- Operation: Runtime type for effect operations
- Resumed: Runtime type for resumption values
- Handler: F-bounded effect interpreter interface
- Perform: Trigger an effect operation
- Handle: Run a computation with an F-bounded effect handler
- HandleFunc: Create a handler from a dispatch function
Standard Effects ¶
All standard handler constructors return concrete types to enable F-bounded inference. Operations implement dispatch methods (e.g. DispatchState) called through structural assertions in handlers.
State effect for mutable state threading:
- Get, Put, Modify: Effect operations
- GetState, PutState, ModifyState: Fused convenience constructors (Cont)
- StateHandler: Creates a State handler (returns *stateHandler and state getter)
- RunState, EvalState, ExecState: Run with State effect (Cont)
- RunStateExpr: Run with State effect (Expr)
Reader effect for read-only environment:
- Ask: Effect operation
- AskReader, MapReader: Fused convenience constructors (Cont)
- ReaderHandler: Creates a Reader handler (returns *readerHandler)
- RunReader: Run with Reader effect (Cont)
- RunReaderExpr: Run with Reader effect (Expr)
Writer effect for accumulating output:
- WriterContext: Shared context for writer dispatch
- Tell, Listen, Censor: Effect operations
- TellWriter: Fused convenience constructor (Cont)
- ListenWriter, CensorWriter: Convenience wrappers (Cont, delegate to Perform)
- WriterHandler: Creates a Writer handler (returns *writerHandler and output getter)
- RunWriter, ExecWriter: Run with Writer effect (Cont)
- RunWriterExpr: Run with Writer effect (Expr)
- Pair: Tuple type for Listen results
Error effect for exception-like control flow:
- Throw, Catch: Effect operations
- ErrorContext: Shared context for error dispatch
- Throw.DispatchError: sets error in context, returns (struct{}{}, true)
- Catch.DispatchError: runs body with RunError internally
- ThrowError, CatchError: Convenience constructors (Cont)
- ExprThrowError: Throw constructor for Expr via direct EffectFrame, not composable from ExprPerform
- RunError: Run with Error effect (Cont), returns Either
- RunErrorExpr: Run with Error effect (Expr), returns Either
Composed Effects ¶
Multi-effect handlers dispatch multiple effect families from a single handler. Combined runners eliminate handler-layer overhead for multi-effect hot paths.
State + Reader:
- RunStateReader: Run with State + Reader (Cont)
- RunStateReaderExpr: Run with State + Reader (Expr)
State + Error (state always available, even on error):
- RunStateError: Run with State + Error (Cont), returns (Either, S)
- EvalStateError: Returns only the Either result
- ExecStateError: Returns only the final state
- RunStateErrorExpr: Run with State + Error (Expr)
State + Writer:
- RunStateWriter: Run with State + Writer (Cont), returns (A, S, []W)
- RunStateWriterExpr: Run with State + Writer (Expr)
Reader + State + Error:
- RunReaderStateError: Run with Reader + State + Error (Cont), returns (Either, S)
- RunReaderStateErrorExpr: Run with Reader + State + Error (Expr)
Either Type ¶
Either represents success (Right) or failure (Left):
- Left, Right: Constructors
- Either.IsLeft, Either.IsRight: Predicates
- Either.GetLeft, Either.GetRight: Accessors
- MatchEither: Pattern matching
- MapEither: Functor map over Right
- FlatMapEither: Monadic bind
- MapLeftEither: Transform Left value
Resource Safety ¶
Exception-safe resource management:
Affine Continuations ¶
Affine wraps a continuation with one-shot enforcement:
- Once: Create an affine continuation
- Affine.Resume: Invoke (panics on reuse)
- Affine.TryResume: Non-panicking variant
- Affine.Discard: Drop without invoking
Bridge: Reify / Reflect ¶
The two representations can be converted at runtime following Filinski (1994): reify converts semantic values to syntactic representations, and reflect is the inverse.
- Reify: Cont[Resumed, A] → Expr[A] (closures become frames)
- Reflect: Expr[A] → Cont[Resumed, A] (frames become closures)
Conversion is lazy for effectful computations: each effect step is translated on demand during evaluation. Round-trip preserves semantics.
Defunctionalized Evaluation ¶
Defunctionalization (Reynolds 1972) enables allocation-free evaluation loops for continuation frames. Instead of closures, continuations are represented as tagged frame structures. The Expr type carries explicit frame data, unlike the closure-based Cont which tracks the answer type R at compile time.
Type-erased values:
- Erased: Type alias for any, marking type-erased intermediate values in the frame chain. Concrete types are recovered via type assertions at frame boundaries. Frame type parameters use Erased (e.g. BindFrame[Erased, Erased]) to document the type-erasure boundary.
Frame is the marker interface for all frame types:
- ReturnFrame: Computation complete
- BindFrame: Monadic sequencing
- MapFrame: Functor transformation
- ThenFrame: Sequencing with discard
- UnwindFrame: Data-oriented frame with static function pointer (fast-path dispatch)
- EffectFrame: Suspended effect operation (carries Operation for dispatch)
Constructors and combinators:
- ExprReturn: Create completed computation
- ExprBind: Sequence computations
- ExprMap: Transform result
- ExprThen: Sequence with discard
- ExprPerform: Perform an effect operation (creates EffectFrame)
- ExprSuspend: Create suspended computation
- ChainFrames: Compose frame chains
- RunPure: Iteratively evaluate pure computation (panics on effects)
- HandleExpr: Evaluate with F-bounded effect handler
Frame Pools ¶
Pool functions acquire pre-allocated frames from sync.Pool for single-use Expr construction. The evaluator releases pooled frames after consumption. Pooled frames assume affine (at-most-once) evaluation; the evaluator zeroes all fields on release. Do not use pooled frames in Expr values that may be evaluated more than once.
- AcquireEffectFrame: Acquire pooled EffectFrame
- AcquireBindFrame: Acquire pooled BindFrame
- AcquireThenFrame: Acquire pooled ThenFrame
- AcquireUnwindFrame: Acquire pooled UnwindFrame
Example ¶
type Ask[A any] struct{ kont.Phantom[A] }
comp := kont.Bind(
kont.Perform(Ask[int]{}),
func(x int) kont.Eff[int] {
return kont.Pure(x * 2)
},
)
result := kont.Handle(comp, kont.HandleFunc[int](func(op kont.Operation) (kont.Resumed, bool) {
switch op.(type) {
case Ask[int]:
return 21, true // resume with 21
default:
panic("unhandled effect")
}
}))
// result == 42
Index ¶
- func EvalState[S, A any](initial S, m Cont[Resumed, A]) A
- func ExecState[S, A any](initial S, m Cont[Resumed, A]) S
- func ExecStateError[S, E, A any](initial S, m Cont[Resumed, A]) S
- func ExecWriter[W, A any](m Cont[Resumed, A]) []W
- func Handle[H Handler[H, R], R any](m Cont[Resumed, R], h H) R
- func HandleExpr[H Handler[H, R], R any](m Expr[R], h H) R
- func HandleFunc[R any](f func(op Operation) (Resumed, bool)) *handlerFunc[R]
- func MatchEither[E, A, T any](e Either[E, A], onLeft func(E) T, onRight func(A) T) T
- func ReaderHandler[E, R any](env E) *readerHandler[E, R]
- func Run[A any](m Cont[A, A]) A
- func RunPure[A any](c Expr[A]) A
- func RunReader[E, A any](env E, m Cont[Resumed, A]) A
- func RunReaderExpr[E, A any](env E, m Expr[A]) A
- func RunState[S, A any](initial S, m Cont[Resumed, A]) (A, S)
- func RunStateExpr[S, A any](initial S, m Expr[A]) (A, S)
- func RunStateReader[S, E, A any](initial S, env E, m Cont[Resumed, A]) (A, S)
- func RunStateReaderExpr[S, E, A any](initial S, env E, m Expr[A]) (A, S)
- func RunStateWriter[S, W, A any](initial S, m Cont[Resumed, A]) (A, S, []W)
- func RunStateWriterExpr[S, W, A any](initial S, m Expr[A]) (A, S, []W)
- func RunWith[R, A any](m Cont[R, A], k func(A) R) R
- func RunWriter[W, A any](m Cont[Resumed, A]) (A, []W)
- func RunWriterExpr[W, A any](m Expr[A]) (A, []W)
- func StateHandler[S, R any](initial S) (*stateHandler[S, R], func() S)
- func WriterHandler[W, R any]() (*writerHandler[W, R], func() []W)
- type Affine
- type Ask
- type BindFrame
- type Catch
- type Censor
- type Cont
- func AskReader[E, B any](f func(E) Cont[Resumed, B]) Cont[Resumed, B]
- func Bind[R, A, B any](m Cont[R, A], f func(A) Cont[R, B]) Cont[R, B]
- func Bracket[E, R, A any](acquire Cont[Resumed, R], release func(R) Cont[Resumed, struct{}], ...) Cont[Resumed, Either[E, A]]
- func CatchError[E, A any](body Cont[Resumed, A], handler func(E) Cont[Resumed, A]) Cont[Resumed, A]
- func CensorWriter[W, A any](f func([]W) []W, body Cont[Resumed, A]) Cont[Resumed, A]
- func GetState[S, B any](f func(S) Cont[Resumed, B]) Cont[Resumed, B]
- func ListenWriter[W, A any](body Cont[Resumed, A]) Cont[Resumed, Pair[A, []W]]
- func Map[R, A, B any](m Cont[R, A], f func(A) B) Cont[R, B]
- func MapReader[E, A any](f func(E) A) Cont[Resumed, A]
- func ModifyState[S, B any](f func(S) S, then func(S) Cont[Resumed, B]) Cont[Resumed, B]
- func OnError[E, A any](body Cont[Resumed, A], cleanup func(E) Cont[Resumed, struct{}]) Cont[Resumed, A]
- func Perform[O Op[O, A], A any](op O) Cont[Resumed, A]
- func PutState[S, B any](s S, next Cont[Resumed, B]) Cont[Resumed, B]
- func Reflect[A any](m Expr[A]) Cont[Resumed, A]
- func Reset[R, A any](m Cont[A, A]) Cont[R, A]
- func Return[R, A any](a A) Cont[R, A]
- func Shift[R, A any](f func(k func(A) R) R) Cont[R, A]
- func Suspend[R, A any](f func(func(A) R) R) Cont[R, A]
- func TellWriter[W, B any](w W, next Cont[Resumed, B]) Cont[Resumed, B]
- func Then[R, A, B any](m Cont[R, A], n Cont[R, B]) Cont[R, B]
- func ThrowError[E, A any](err E) Cont[Resumed, A]
- type Eff
- type EffectFrame
- type Either
- func EvalStateError[S, E, A any](initial S, m Cont[Resumed, A]) Either[E, A]
- func FlatMapEither[E, A, B any](e Either[E, A], f func(A) Either[E, B]) Either[E, B]
- func Left[E, A any](e E) Either[E, A]
- func MapEither[E, A, B any](e Either[E, A], f func(A) B) Either[E, B]
- func MapLeftEither[E, F, A any](e Either[E, A], f func(E) F) Either[F, A]
- func Right[E, A any](a A) Either[E, A]
- func RunError[E, A any](m Cont[Resumed, A]) Either[E, A]
- func RunErrorExpr[E, A any](m Expr[A]) Either[E, A]
- func RunReaderStateError[Env, S, Err, A any](env Env, initial S, m Cont[Resumed, A]) (Either[Err, A], S)
- func RunReaderStateErrorExpr[Env, S, Err, A any](env Env, initial S, m Expr[A]) (Either[Err, A], S)
- func RunStateError[S, E, A any](initial S, m Cont[Resumed, A]) (Either[E, A], S)
- func RunStateErrorExpr[S, E, A any](initial S, m Expr[A]) (Either[E, A], S)
- type Erased
- type ErrorContext
- type Expr
- func ExprBind[A, B any](m Expr[A], f func(A) Expr[B]) Expr[B]
- func ExprMap[A, B any](m Expr[A], f func(A) B) Expr[B]
- func ExprPerform[O Op[O, A], A any](op O) Expr[A]
- func ExprReturn[A any](a A) Expr[A]
- func ExprSuspend[A any](frame Frame) Expr[A]
- func ExprThen[A, B any](m Expr[A], n Expr[B]) Expr[B]
- func ExprThrowError[E, A any](err E) Expr[A]
- func Reify[A any](m Cont[Resumed, A]) Expr[A]
- type Frame
- type Get
- type Handler
- type Listen
- type MapFrame
- type Modify
- type Op
- type Operation
- type Pair
- type Phantom
- type Put
- type Resumed
- type ReturnFrame
- type Suspension
- type Tell
- type ThenFrame
- type Throw
- type UnwindFrame
- type WriterContext
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ExecStateError ¶
ExecStateError runs a State+Error computation and returns only the final state.
func ExecWriter ¶
ExecWriter runs a writer computation and returns only the output.
func Handle ¶
Handle runs a computation with an F-bounded effect handler. The handler intercepts effect operations and determines how to resume.
Example:
result := Handle(computation, HandleFunc[int](func(op Operation) (Resumed, bool) {
switch op.(type) {
case Ask[int]:
return 42, true
default:
panic("unhandled effect")
}
}))
func HandleExpr ¶
HandleExpr evaluates a defunctionalized computation with an effect handler. This is the Expr counterpart of Handle for closure-based Cont.
Like RunPure, it processes frames iteratively without stack growth. When encountering an EffectFrame, it dispatches the operation to the handler. The handler returns (resumeValue, true) to continue, or (finalResult, false) to short-circuit.
func HandleFunc ¶
HandleFunc creates a handler from a dispatch function. The function receives each effect operation and returns (resumeValue, true) to continue the computation, or (finalResult, false) to short-circuit.
Example:
HandleFunc[int](func(op Operation) (Resumed, bool) {
switch e := op.(type) {
case Ask[int]:
return 42, true // resume with value
case Tell[int]:
fmt.Println(e.Value)
return struct{}{}, true
default:
panic("unhandled effect")
}
})
func MatchEither ¶
MatchEither pattern matches on the Either, calling onLeft or onRight.
func ReaderHandler ¶
func ReaderHandler[E, R any](env E) *readerHandler[E, R]
ReaderHandler creates a handler for Reader effects with the given environment. Returns a concrete handler.
func Run ¶
Run executes a continuation with the identity continuation. The result type must match the value type (R = A).
func RunPure ¶
RunPure evaluates a pure defunctionalized computation to completion. It iteratively processes frames until reaching ReturnFrame, avoiding stack growth from recursive calls.
Panics if the computation contains EffectFrame. Use HandleExpr for computations with effects.
func RunReaderExpr ¶
RunReaderExpr runs an Expr computation with the given environment.
func RunStateExpr ¶
RunStateExpr runs a stateful Expr computation.
func RunStateReader ¶
RunStateReader runs a computation with both State and Reader effects. Returns the result and final state.
func RunStateReaderExpr ¶
RunStateReaderExpr runs an Expr with both State and Reader effects.
func RunStateWriter ¶
RunStateWriter runs a computation with both State and Writer effects. Returns (A, S, []W). Both effects always resume — no short-circuit.
func RunStateWriterExpr ¶
RunStateWriterExpr runs an Expr with both State and Writer effects.
func RunWriterExpr ¶
RunWriterExpr runs an Expr writer computation.
func StateHandler ¶
func StateHandler[S, R any](initial S) (*stateHandler[S, R], func() S)
StateHandler creates a handler for State effects with the given initial state. Returns a concrete handler and a function to retrieve the current state.
func WriterHandler ¶
func WriterHandler[W, R any]() (*writerHandler[W, R], func() []W)
WriterHandler creates a handler for Writer effects. Returns a concrete handler and a function to retrieve accumulated output.
Types ¶
type Affine ¶
type Affine[R, A any] struct { // contains filtered or unexported fields }
Affine wraps a continuation with one-shot enforcement. The continuation can be resumed at most once; subsequent attempts to resume will panic (Resume) or return false (TryResume).
Affine types model affine resource usage and are fundamental to algebraic effect handlers where continuations must not be duplicated.
func Once ¶
Once creates an affine continuation from a regular continuation. The returned Affine can be resumed at most once.
func (*Affine[R, A]) Discard ¶
func (a *Affine[R, A]) Discard()
Discard marks the continuation as used without invoking it. This is useful for explicitly dropping a continuation that will not be used.
type Ask ¶
type Ask[E any] struct{}
Ask is the effect operation for reading the environment. Perform(Ask[E]{}) returns the current environment of type E.
func (Ask[E]) DispatchReader ¶
DispatchReader handles Ask in Reader handler dispatch.
type BindFrame ¶
type BindFrame[A, B any] struct { // F is the continuation function to apply to the input value. F func(A) Expr[B] // Next is the continuation frame after F completes. Next Frame // contains filtered or unexported fields }
BindFrame represents monadic bind: Bind(m, f) Type parameters:
- A: input type (value from previous computation)
- B: output type (result of applying F)
func AcquireBindFrame ¶ added in v0.1.2
AcquireBindFrame acquires a pooled single-use BindFrame[Erased, Erased] whose F and Next fields must be filled before evaluation.
type Catch ¶
Catch is the effect operation for handling errors. Perform(Catch[E, A]{Body: m, Handler: h}) runs m, catching errors with h.
Like Listen/Censor, Catch runs the body with an error-only handler internally. Other effects (State, Reader, Writer) in the catch body are not handled.
func (Catch[E, A]) DispatchError ¶
func (o Catch[E, A]) DispatchError(ctx *ErrorContext[E]) (Resumed, bool)
DispatchError handles Catch in Error handler dispatch. Runs the body with RunError internally (like Listen/Censor pattern). Other effects in the catch body/handler are not handled.
type Censor ¶
Censor is the effect operation for modifying output. Perform(Censor[W, A]{F: f, Body: m}) runs m and applies f to its output.
Note: Like Listen, Censor[W, A] for all A implements DispatchWriter.
func (Censor[W, A]) DispatchWriter ¶
func (o Censor[W, A]) DispatchWriter(ctx *WriterContext[W]) (Resumed, bool)
DispatchWriter handles Censor in Writer handler dispatch.
type Cont ¶
type Cont[R, A any] func(k func(A) R) R
Cont represents a continuation-passing computation. Cont[R, A] computes a value of type A, with final result type R.
The function receives a continuation k of type func(A) R, which represents "the rest of the computation". Applying k to a value of type A produces the final result of type R.
func Bind ¶
Bind sequences two continuations (monadic bind). It runs m, then passes the result to f to get a new continuation.
func Bracket ¶
func Bracket[E, R, A any]( acquire Cont[Resumed, R], release func(R) Cont[Resumed, struct{}], use func(R) Cont[Resumed, A], ) Cont[Resumed, Either[E, A]]
Bracket provides exception-safe resource acquisition and release. This follows the bracket pattern: acquire → use → release, where release is guaranteed to run even if use raises an error.
Returns Either containing the result or the error.
func CatchError ¶
CatchError wraps a computation with an error handler.
func CensorWriter ¶
CensorWriter runs a computation and modifies its output.
func ListenWriter ¶
ListenWriter runs a computation and returns its output alongside the result.
func Map ¶
Map applies a pure function to the result of a continuation.
Allocation note: Map is equivalent to Bind(m, compose(Return, f)) but avoids the intermediate Return closure, making it the preferred choice when the transformation is pure (does not produce effects).
func ModifyState ¶
ModifyState fuses Modify + Bind: performs Modify, passes new state to f.
func OnError ¶
func OnError[E, A any]( body Cont[Resumed, A], cleanup func(E) Cont[Resumed, struct{}], ) Cont[Resumed, A]
OnError runs cleanup only if the computation throws an error.
func Perform ¶
Perform triggers an effect operation and suspends the computation. The handler receives the operation via [Handler.Dispatch] and provides a resume value, or short-circuits with a final result.
func Reflect ¶
Reflect converts a defunctionalized frame chain back into a closure-based effectful computation. Tagged data becomes closures.
The resulting Cont[Resumed, A] can be used with Handle, RunState, RunReader, and all other Cont-world runners.
The name follows Filinski (1994): reflect converts a syntactic representation (data Expr) into a semantic value (functional Cont).
Example:
expr := ExprBind(ExprPerform(Get[int]{}), func(s int) Expr[int] {
return ExprReturn(s * 2)
})
cont := Reflect(expr)
result, state := RunState[int, int](0, cont)
func Reset ¶
Reset establishes a delimiter for Shift. Continuations captured by Shift stop at the nearest enclosing Reset.
func Return ¶
Return lifts a pure value into the continuation monad. The resulting computation immediately passes the value to its continuation.
func Shift ¶
Shift captures the current continuation up to the nearest Reset. The function f receives the captured continuation k, which can be invoked zero or more times.
Example:
Reset(Bind(Shift(func(k func(int) int) int {
return k(k(3)) // Apply continuation twice
}), func(x int) Cont[int, int] {
return Return[int](x * 2)
}))
// Result: 12 (3 * 2 * 2)
func Suspend ¶
Suspend creates a continuation from a CPS function. This is the primitive constructor for continuations that need direct access to the continuation.
func TellWriter ¶
TellWriter fuses Tell + Then: performs Tell, then runs next.
func Then ¶
Then sequences two continuations, discarding the first result. This is more efficient than Bind when the second computation does not depend on the first result.
Allocation note: Then avoids the closure capture of a transformation function that would occur with Bind(m, func(_ A) { return n }).
func ThrowError ¶
ThrowError performs the Throw effect to raise an error. This aborts the current computation — the continuation k is never called.
type Eff ¶ added in v0.1.1
Eff is an effectful computation that produces a value of type A. This is the most common continuation type in effectful code.
type EffectFrame ¶
type EffectFrame[A any] struct { // Operation is the effect operation for handler dispatch. Operation Operation // Resume is called with the handler's response value. Resume func(A) Erased // Next is the continuation frame after resumption. Next Frame // contains filtered or unexported fields }
EffectFrame represents a suspended effect operation. The handler dispatches on the operation and resumes with a value. Type parameters:
- A: the type the operation produces when resumed
func AcquireEffectFrame ¶ added in v0.1.2
func AcquireEffectFrame() *EffectFrame[Erased]
AcquireEffectFrame acquires a pooled single-use EffectFrame[Erased] whose Operation, Resume, and Next fields must be filled before evaluation.
type Either ¶
type Either[E, A any] struct { // contains filtered or unexported fields }
Either represents a value that is either Left (error) or Right (success).
func EvalStateError ¶
EvalStateError runs a State+Error computation and returns only the Either result.
func FlatMapEither ¶
FlatMapEither sequences two Either computations.
func MapLeftEither ¶
MapLeftEither applies a function to the Left value.
func RunErrorExpr ¶
RunErrorExpr runs an Expr that may throw errors, returning Either. Handles Throw and Catch. Catch runs body with error-only handler internally.
func RunReaderStateError ¶
func RunReaderStateError[Env, S, Err, A any](env Env, initial S, m Cont[Resumed, A]) (Either[Err, A], S)
RunReaderStateError runs a computation with Reader, State, and Error effects. Dispatch order: Reader → State → Error. Returns (Either[Err, A], S).
func RunReaderStateErrorExpr ¶
RunReaderStateErrorExpr runs an Expr with Reader, State, and Error effects. Handles Throw and Catch. Catch runs body with error-only handler internally.
func RunStateError ¶
RunStateError runs a computation with both State and Error effects. Returns (Either[E, A], S) — state is always available, even on error.
func RunStateErrorExpr ¶
RunStateErrorExpr runs an Expr with both State and Error effects. Handles Throw and Catch. Catch runs body with error-only handler internally.
type Erased ¶
type Erased = any
Erased represents a type-erased value in the defunctionalized frame chain. Frame types use Erased parameters to process heterogeneous value types through a homogeneous evaluation pipeline. Concrete types are recovered via type assertions at frame boundaries.
type ErrorContext ¶
ErrorContext holds the state needed for Error effect dispatch.
type Expr ¶
type Expr[A any] struct { // Value holds the current value if this is a completed computation. // Valid when Frame is ReturnFrame. Value A // Frame holds the next continuation frame. Frame Frame }
Expr is a defunctionalized continuation. Unlike the closure-based Cont[R, A], this carries explicit frame data.
func ExprPerform ¶
ExprPerform creates a defunctionalized computation that performs an effect operation. This is the Expr counterpart of Perform for closure-based Cont.
The computation suspends at an EffectFrame carrying the operation. Use HandleExpr to evaluate computations containing effect frames.
Type inference handles calls: ExprPerform(Get[int]{}) infers O=Get[int], A=int.
func ExprReturn ¶
ExprReturn creates a completed computation with the given value.
func ExprSuspend ¶
ExprSuspend creates a computation suspended at the given frame.
func ExprThrowError ¶
ExprThrowError creates an Expr that throws an error. Constructs EffectFrame directly because Throw[E].OpResult() returns Resumed, not A — ExprPerform would produce Expr[Resumed].
func Reify ¶
Reify converts a closure-based effectful computation into a defunctionalized frame chain. Closures become tagged data.
The conversion is lazy: each effect step is converted on demand as the Expr is evaluated. Pure computations are converted eagerly.
The name follows Filinski (1994): reify converts a semantic value (functional Cont) into its syntactic representation (data Expr).
Example:
cont := GetState(func(s int) Eff[int] {
return Pure(s * 2)
})
expr := Reify(cont)
result, state := RunStateExpr[int, int](0, expr)
type Frame ¶
type Frame interface {
// contains filtered or unexported methods
}
Frame is the interface for defunctionalized continuation frames. Implementations carry the data needed to continue computation. Dispatch uses type switches, not tags — Frame is a pure marker interface.
func ChainFrames ¶
ChainFrames links two frame chains together. Returns the other operand when either side is ReturnFrame (the identity element for frame composition), avoiding unnecessary chainedFrame allocation.
Construction is O(1) in all cases: returns the other operand or creates one chainedFrame node.
type Get ¶
type Get[S any] struct{}
Get is the effect operation for reading state. Perform(Get[S]{}) returns the current state of type S.
func (Get[S]) DispatchState ¶
DispatchState handles Get in State handler dispatch.
type Handler ¶
Handler is the F-bounded interface for effect handlers. The self-referencing constraint H Handler[H, R] gives the compiler knowledge of the concrete handler type at compile time.
The Dispatch method returns (resumeValue, true) to continue the computation, or (finalResult, false) to short-circuit and return immediately.
type Listen ¶
Listen is the effect operation for observing output. Perform(Listen[W, A]{Body: m}) runs m and returns its output alongside result.
Note: Listen[W, A] for all A implements DispatchWriter through structural interface assertion. This fixes the type switch limitation where case Listen[W, Resumed] won't match Listen[W, int].
func (Listen[W, A]) DispatchWriter ¶
func (o Listen[W, A]) DispatchWriter(ctx *WriterContext[W]) (Resumed, bool)
DispatchWriter handles Listen in Writer handler dispatch. Listen[W, A] for all A dispatches through structural interface assertion.
type MapFrame ¶
type MapFrame[A, B any] struct { // F is the transformation function. F func(A) B // Next is the continuation frame after transformation. Next Frame }
MapFrame represents functor mapping: Map(m, f) Type parameters:
- A: input type (value to transform)
- B: output type (result of transformation)
type Modify ¶
type Modify[S any] struct{ F func(S) S }
Modify is the effect operation for modifying state. Perform(Modify[S]{F: f}) applies f to state and returns the new state.
func (Modify[S]) DispatchState ¶
DispatchState handles Modify in State handler dispatch.
type Op ¶
Op is the F-bounded interface for effect operations. Each effect defines concrete types implementing Op with the appropriate result type parameter. The self-referencing constraint gives the compiler knowledge of both the concrete operation type and its result type.
Example:
type Ask[E any] struct{ kont.Phantom[E] }
type Operation ¶
type Operation any
Operation is the interface for effect operations in handler dispatch. All values passed as the op parameter to Handler.Dispatch implement this interface.
type Phantom ¶ added in v0.1.1
type Phantom[A any] struct{}
Phantom is an embeddable zero-size type that provides the Op result marker. Embed Phantom[A] in an operation struct to satisfy Op without writing a manual OpResult method.
Example:
type Ask[E any] struct{ kont.Phantom[E] }
// Ask[E] satisfies Op[Ask[E], E] via promoted OpResult() E
type Put ¶
type Put[S any] struct{ Value S }
Put is the effect operation for writing state. Perform(Put[S]{Value: s}) replaces the current state.
func (Put[S]) DispatchState ¶
DispatchState handles Put in State handler dispatch.
type Resumed ¶
type Resumed any
Resumed is the interface for values flowing through effect suspension and resumption. Effectful computations use Cont[Resumed, A] as their continuation type. Handler resume callbacks accept and return Resumed.
type ReturnFrame ¶
type ReturnFrame struct{}
ReturnFrame signals computation completion. The evaluator returns the current value as the final result.
type Suspension ¶
type Suspension[A any] struct { // contains filtered or unexported fields }
Suspension represents a computation suspended on an effect operation. It holds the pending operation and a one-shot resumption handle.
Suspension enforces affine semantics: Resume may be called at most once. Calling Resume twice panics. Use Discard to explicitly abandon a suspension.
func Step ¶
func Step[A any](m Cont[Resumed, A]) (A, *Suspension[A])
Step drives a Cont[Resumed, A] computation until it either completes or suspends on an effect operation. Returns (value, nil) if the computation completed, or (zero, suspension) if pending.
Example:
result, susp := Step(computation)
for susp != nil {
v := handleOp(susp.Op())
result, susp = susp.Resume(v)
}
func StepExpr ¶
func StepExpr[A any](m Expr[A]) (A, *Suspension[A])
StepExpr drives an Expr[A] computation until it either completes or suspends on an effect operation. Returns (value, nil) if the computation completed, or (zero, suspension) if pending.
func (*Suspension[A]) Discard ¶
func (s *Suspension[A]) Discard()
Discard marks the suspension as consumed without resuming.
func (*Suspension[A]) Op ¶
func (s *Suspension[A]) Op() Operation
Op returns the effect operation that caused the suspension.
func (*Suspension[A]) Resume ¶
func (s *Suspension[A]) Resume(v Resumed) (A, *Suspension[A])
Resume advances the computation with the given value. Returns either a completed value (with nil suspension) or the next suspension. Panics if the suspension has already been resumed or discarded.
On the Expr path, the returned suspension reuses the receiver's memory when possible, avoiding one allocation per step.
func (*Suspension[A]) TryResume ¶
func (s *Suspension[A]) TryResume(v Resumed) (A, *Suspension[A], bool)
TryResume attempts to advance the computation. Returns (value, suspension, true) on success, or (zero, nil, false) if already used.
type Tell ¶
type Tell[W any] struct{ Value W }
Tell is the effect operation for appending output. Perform(Tell[W]{Value: w}) appends w to the accumulated output.
func (Tell[W]) DispatchWriter ¶
func (o Tell[W]) DispatchWriter(ctx *WriterContext[W]) (Resumed, bool)
DispatchWriter handles Tell in Writer handler dispatch.
type ThenFrame ¶
type ThenFrame[A, B any] struct { // Second is the computation to evaluate after discarding first result. Second Expr[B] // Next is the continuation frame after Second completes. Next Frame // contains filtered or unexported fields }
ThenFrame represents sequencing with discard: Then(m, n) Type parameters:
- A: discarded type (result of first computation, unused)
- B: output type (result of second computation)
func AcquireThenFrame ¶ added in v0.1.2
AcquireThenFrame acquires a pooled single-use ThenFrame[Erased, Erased] whose Second and Next fields must be filled before evaluation.
type Throw ¶
type Throw[E any] struct{ Err E }
Throw is the effect operation for raising an error. Perform(Throw[E]{Err: e}) aborts the computation with error e.
func (Throw[E]) DispatchError ¶
func (o Throw[E]) DispatchError(ctx *ErrorContext[E]) (Resumed, bool)
DispatchError handles Throw in Error handler dispatch. Sets the error in the context and returns (struct{}{}, true) — uniform with State/Reader/Writer. The handler inspects ctx.HasErr to short-circuit.
type UnwindFrame ¶ added in v0.1.3
type UnwindFrame struct {
Data1 Erased
Data2 Erased
Data3 Erased
// Unwind computes the next value and frame using the stored data and current value.
Unwind func(Data1, Data2, Data3, current Erased) (Erased, Frame)
// contains filtered or unexported fields
}
UnwindFrame represents an unrolled continuation frame that avoids closure allocation. It stores up to three type-erased variables alongside a function pointer, and is evaluated directly in the trampoline fast-path without interface type assertions. The 3 data fields (any, 16 B each), function pointer (8 B), and pooled flag (1 B + 7 B pad) total 64 bytes on amd64 — exactly one cache line.
func AcquireUnwindFrame ¶ added in v0.1.3
func AcquireUnwindFrame() *UnwindFrame
AcquireUnwindFrame acquires a pooled single-use UnwindFrame whose Unwind field (and any necessary Data fields) must be filled before evaluation.
type WriterContext ¶
type WriterContext[W any] struct { Output *[]W }
WriterContext holds the state needed for Writer effect dispatch.