kont

package module
v0.1.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 27, 2026 License: MIT Imports: 2 Imported by: 2

README

Go Reference Go Report Card Coverage Status

English | 简体中文 | Español | 日本語 | Français

kont

Delimited continuations and algebraic effects for Go via F-bounded polymorphism.

Overview

kont provides:

  • Minimal but complete interfaces for continuations, control, and effects
  • F-bounded polymorphism for compile-time dispatch and devirtualization
  • Defunctionalized evaluation with an allocation-free evaluation loop
Theoretical Foundations
Concept Reference Implementation
Continuation Monad Moggi (1989) Cont[R, A]
Delimited Continuations Danvy & Filinski (1990) Shift, Reset
Algebraic Effects Plotkin & Pretnar (2009) Op, Handler, Perform, Handle
Affine Types Walker & Watkins (2001) Affine[R, A]
Representing Monads Filinski (1994) Reify, Reflect
Defunctionalization Reynolds (1972) Expr[A], Frame

Installation

go get code.hybscloud.com/kont

Requires Go 1.26+.

Core Types

Type Purpose
Cont[R, A] CPS computation: func(func(A) R) R
Eff[A] Effectful computation: type alias for Cont[Resumed, A]
Pure Lift a value into Eff with full type inference
Expr[A] Defunctionalized computation (allocation-free evaluation loop)
Shift/Reset Delimited control operators
Op[O Op[O, A], A] F-bounded effect operation interface
Handler[H Handler[H, R], R] F-bounded effect handler interface
Either[E, A] Sum type for error handling
Affine[R, A] One-shot continuation
Erased Type alias for any marking type-erased frame values
Reify/Reflect Bridge: Cont ↔ Expr (Filinski 1994)

Basic Usage

If you are new to kont, start with Return/Bind/Run to learn composition, then adopt standard effect runners (State, Reader, Writer, Error), and finally move to Expr/Step APIs for allocation-sensitive hot paths or externally driven runtimes.

Return and Run
m := kont.Return[int](42)
result := kont.Run(m) // 42
Bind (Monadic Composition)
m := kont.Bind(
    kont.Return[int](21),
    func(x int) kont.Cont[int, int] {
        return kont.Return[int](x * 2)
    },
)
result := kont.Run(m) // 42
Shift and Reset
m := kont.Reset[int](
    kont.Bind(
        kont.Shift[int, int](func(k func(int) int) int {
            return k(1) + k(10)
        }),
        func(x int) kont.Cont[int, int] {
            return kont.Return[int](x * 2)
        },
    ),
)
result := kont.Run(m) // (1*2) + (10*2) = 22

Standard Effects

State
comp := kont.GetState(func(s int) kont.Eff[int] {
    return kont.PutState(s+10, kont.Perform(kont.Get[int]{}))
})
result, state := kont.RunState[int, int](0, comp)
Reader
comp := kont.AskReader(func(cfg Config) kont.Eff[string] {
    return kont.Pure(cfg.BaseURL)
})
result := kont.RunReader(config, comp)
Writer
comp := kont.TellWriter("log message", kont.Pure(42))
result, logs := kont.RunWriter[string, int](comp)
Error
comp := kont.CatchError[string, int](
    kont.ThrowError[string, int]("error"),
    func(err string) kont.Eff[int] {
        return kont.Pure(0)
    },
)
result := kont.RunError[string, int](comp)

Stepping

Step and StepExpr provide one-effect-at-a-time evaluation for external runtimes.

Nil completion convention: the stepping boundary and effect runners 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 them.

result, susp := kont.Step(computation)
for susp != nil {
    op := susp.Op()        // observe pending operation
    v := execute(op)        // external runtime handles the operation
    result, susp = susp.Resume(v) // advance to next suspension
}
// result is the final value

Expr equivalent:

result, susp := kont.StepExpr(exprComputation)

Each suspension is one-shot: Resume panics on reuse.

Composed Effects

Combined runners dispatch multiple effect families from a single handler.

// State + Reader
result, state := kont.RunStateReader[int, string, int](0, "env", comp)

// State + Error (state always available, even on error)
result, state := kont.RunStateError[int, string, int](0, comp) // result: Either[string, int]

// State + Writer
result, state, logs := kont.RunStateWriter[int, string, int](0, comp)

// Reader + State + Error
result, state := kont.RunReaderStateError[string, int, string, int]("env", 0, comp)

All composed runners have Expr equivalents (RunStateReaderExpr, RunStateErrorExpr, RunStateWriterExpr, RunReaderStateErrorExpr).

Resource Safety

Bracket
comp := kont.Bracket[error, *File, string](
    acquire,
    func(f *File) kont.Eff[struct{}] {
        f.Close()
        return kont.Pure(struct{}{})
    },
    func(f *File) kont.Eff[string] {
        return kont.Pure(f.ReadAll())
    },
)
OnError
comp := kont.OnError(riskyOp(), errorCleanup)

Defunctionalized Evaluation

Closures become tagged frame data structures. An iterative trampoline evaluator processes them without stack growth. The evaluation loop is allocation-free; frame construction may allocate.

Return and Map
c := kont.ExprReturn(42)
c = kont.ExprMap(c, func(x int) int { return x * 2 })
result := kont.RunPure(c) // 84
Bind
c := kont.ExprReturn(10)
c = kont.ExprBind(c, func(x int) kont.Expr[string] {
    return kont.ExprReturn(fmt.Sprintf("value=%d", x))
})
result := kont.RunPure(c) // "value=10"
Multi-Stage Pipeline
c := kont.ExprReturn(1)
c = kont.ExprBind(c, func(x int) kont.Expr[int] {
    return kont.ExprReturn(x + 1)
})
c = kont.ExprMap(c, func(x int) int { return x * 3 })
c = kont.ExprBind(c, func(x int) kont.Expr[int] {
    return kont.ExprMap(kont.ExprReturn(x), func(y int) int { return y + 10 })
})
result := kont.RunPure(c) // ((1+1)*3)+10 = 16
Then
first := kont.ExprReturn("ignored")
second := kont.ExprReturn(42)
c := kont.ExprThen(first, second)
result := kont.RunPure(c) // 42
Expr Effects

Expr computations support the same standard effects via HandleExpr and dedicated runners. Compose ExprBind/ExprThen/ExprMap with ExprPerform directly:

// s := Get; Put(s+10); Get
comp := kont.ExprBind(kont.ExprPerform(kont.Get[int]{}), func(s int) kont.Expr[int] {
    return kont.ExprThen(kont.ExprPerform(kont.Put[int]{Value: s + 10}),
        kont.ExprPerform(kont.Get[int]{}))
})
result, state := kont.RunStateExpr[int, int](0, comp)
// Reader
comp := kont.ExprBind(kont.ExprPerform(kont.Ask[string]{}), func(env string) kont.Expr[string] {
    return kont.ExprReturn(env)
})
result := kont.RunReaderExpr[string, string]("hello", comp)
// Writer
comp := kont.ExprThen(kont.ExprPerform(kont.Tell[string]{Value: "log"}),
    kont.ExprReturn(42))
result, logs := kont.RunWriterExpr[string, int](comp)
// Error
result := kont.RunErrorExpr[string, int](kont.ExprThrowError[string, int]("fail"))
// result.IsLeft() == true
Direct Frame Construction

For advanced use, build and evaluate frame chains directly:

expr := kont.Expr[int]{
    Value: 5,
    Frame: &kont.MapFrame[kont.Erased, kont.Erased]{
        F:    func(v kont.Erased) kont.Erased { return v.(int) * 10 },
        Next: kont.ReturnFrame{},
    },
}
result := kont.RunPure(expr) // 50

Reify / Reflect

Convert between the two representations at runtime (Filinski 1994).

// Cont → Expr (closures become frames)
cont := kont.GetState(func(s int) kont.Eff[int] {
    return kont.Pure(s * 2)
})
expr := kont.Reify(cont)
result, state := kont.RunStateExpr[int, int](5, expr)

// Expr → Cont (frames become closures)
expr := kont.ExprBind(kont.ExprPerform(kont.Get[int]{}), func(s int) kont.Expr[int] {
    return kont.ExprReturn(s * 2)
})
cont := kont.Reflect(expr)
result, state := kont.RunState[int, int](5, cont)

Round-trip preserves semantics: Reify ∘ Reflect ≡ id and Reflect ∘ Reify ≡ id.

References

License

MIT License. See LICENSE for details.

©2026 Hayabusa Cloud Co., Ltd.

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:

  • Return: Lift a pure value into a continuation
  • Bind: Sequence two continuations

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”.

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:

Reader effect for read-only environment:

Writer effect for accumulating output:

Error effect for exception-like control flow:

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:

State + Error (state always available, even on error):

State + Writer:

Reader + State + Error:

Either Type

Either represents success (Right) or failure (Left):

Resource Safety

Exception-safe resource management:

  • Bracket: Acquire-release-use with guaranteed cleanup
  • OnError: Run cleanup only on error

Affine Continuations

Affine wraps a continuation with one-shot enforcement:

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:

Constructors and combinators:

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.

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

Constants

This section is empty.

Variables

This section is empty.

Functions

func EvalState

func EvalState[S, A any](initial S, m Cont[Resumed, A]) A

EvalState runs a stateful computation and returns only the result.

func ExecState

func ExecState[S, A any](initial S, m Cont[Resumed, A]) S

ExecState runs a stateful computation and returns only the final state.

func ExecStateError

func ExecStateError[S, E, A any](initial S, m Cont[Resumed, A]) S

ExecStateError runs a State+Error computation and returns only the final state.

func ExecWriter

func ExecWriter[W, A any](m Cont[Resumed, A]) []W

ExecWriter runs a writer computation and returns only the output.

func Handle

func Handle[H Handler[H, R], R any](m Cont[Resumed, R], h H) R

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

func HandleExpr[H Handler[H, R], R any](m Expr[R], h H) R

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

func HandleFunc[R any](f func(op Operation) (Resumed, bool)) *handlerFunc[R]

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

func MatchEither[E, A, T any](e Either[E, A], onLeft func(E) T, onRight func(A) T) T

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

func Run[A any](m Cont[A, A]) A

Run executes a continuation with the identity continuation. The result type must match the value type (R = A).

func RunPure

func RunPure[A any](c Expr[A]) A

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 RunReader

func RunReader[E, A any](env E, m Cont[Resumed, A]) A

RunReader runs a computation with the given environment.

func RunReaderExpr

func RunReaderExpr[E, A any](env E, m Expr[A]) A

RunReaderExpr runs an Expr computation with the given environment.

func RunState

func RunState[S, A any](initial S, m Cont[Resumed, A]) (A, S)

RunState runs a stateful computation and returns both the result and final state.

func RunStateExpr

func RunStateExpr[S, A any](initial S, m Expr[A]) (A, S)

RunStateExpr runs a stateful Expr computation.

func RunStateReader

func RunStateReader[S, E, A any](initial S, env E, m Cont[Resumed, A]) (A, S)

RunStateReader runs a computation with both State and Reader effects. Returns the result and final state.

func RunStateReaderExpr

func RunStateReaderExpr[S, E, A any](initial S, env E, m Expr[A]) (A, S)

RunStateReaderExpr runs an Expr with both State and Reader effects.

func RunStateWriter

func RunStateWriter[S, W, A any](initial S, m Cont[Resumed, A]) (A, S, []W)

RunStateWriter runs a computation with both State and Writer effects. Returns (A, S, []W). Both effects always resume — no short-circuit.

func RunStateWriterExpr

func RunStateWriterExpr[S, W, A any](initial S, m Expr[A]) (A, S, []W)

RunStateWriterExpr runs an Expr with both State and Writer effects.

func RunWith

func RunWith[R, A any](m Cont[R, A], k func(A) R) R

RunWith executes a continuation with a custom final continuation.

func RunWriter

func RunWriter[W, A any](m Cont[Resumed, A]) (A, []W)

RunWriter runs a writer computation and returns both result and output.

func RunWriterExpr

func RunWriterExpr[W, A any](m Expr[A]) (A, []W)

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

func Once[R, A any](k func(A) R) *Affine[R, A]

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.

func (*Affine[R, A]) Resume

func (a *Affine[R, A]) Resume(v A) R

Resume invokes the continuation with the given value. Panics if the continuation has already been used.

func (*Affine[R, A]) TryResume

func (a *Affine[R, A]) TryResume(v A) (R, bool)

TryResume attempts to invoke the continuation. Returns (result, true) on success, or (zero, false) if already 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

func (Ask[E]) DispatchReader(env *E) (Resumed, bool)

DispatchReader handles Ask in Reader handler dispatch.

func (Ask[E]) OpResult

func (Ask[E]) OpResult() E

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

func AcquireBindFrame() *BindFrame[Erased, Erased]

AcquireBindFrame acquires a pooled single-use BindFrame[Erased, Erased] whose F and Next fields must be filled before evaluation.

func (*BindFrame[A, B]) Unwind

func (f *BindFrame[A, B]) Unwind(current Erased) (Erased, Frame)

Unwind performs a single step of reduction for the BindFrame.

type Catch

type Catch[E, A any] struct {
	Body    Cont[Resumed, A]
	Handler func(E) Cont[Resumed, A]
}

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.

func (Catch[E, A]) OpResult

func (Catch[E, A]) OpResult() A

type Censor

type Censor[W, A any] struct {
	F    func([]W) []W
	Body Cont[Resumed, A]
}

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.

func (Censor[W, A]) OpResult

func (Censor[W, A]) OpResult() A

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 AskReader

func AskReader[E, B any](f func(E) Cont[Resumed, B]) Cont[Resumed, B]

AskReader fuses Ask + Bind: performs Ask, passes environment to f.

func Bind

func Bind[R, A, B any](m Cont[R, A], f func(A) Cont[R, B]) Cont[R, B]

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

func CatchError[E, A any](body Cont[Resumed, A], handler func(E) Cont[Resumed, A]) Cont[Resumed, A]

CatchError wraps a computation with an error handler.

func CensorWriter

func CensorWriter[W, A any](f func([]W) []W, body Cont[Resumed, A]) Cont[Resumed, A]

CensorWriter runs a computation and modifies its output.

func GetState

func GetState[S, B any](f func(S) Cont[Resumed, B]) Cont[Resumed, B]

GetState fuses Get + Bind: performs Get, passes state to f.

func ListenWriter

func ListenWriter[W, A any](body Cont[Resumed, A]) Cont[Resumed, Pair[A, []W]]

ListenWriter runs a computation and returns its output alongside the result.

func Map

func Map[R, A, B any](m Cont[R, A], f func(A) B) Cont[R, B]

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 MapReader

func MapReader[E, A any](f func(E) A) Cont[Resumed, A]

MapReader fuses Ask + Map: performs Ask, applies projection f.

func ModifyState

func ModifyState[S, B any](f func(S) S, then func(S) Cont[Resumed, B]) Cont[Resumed, B]

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

func Perform[O Op[O, A], A any](op O) Cont[Resumed, A]

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 PutState

func PutState[S, B any](s S, next Cont[Resumed, B]) Cont[Resumed, B]

PutState fuses Put + Then: performs Put, then runs next.

func Reflect

func Reflect[A any](m Expr[A]) Cont[Resumed, A]

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

func Reset[R, A any](m Cont[A, A]) Cont[R, A]

Reset establishes a delimiter for Shift. Continuations captured by Shift stop at the nearest enclosing Reset.

func Return

func Return[R, A any](a A) Cont[R, A]

Return lifts a pure value into the continuation monad. The resulting computation immediately passes the value to its continuation.

func Shift

func Shift[R, A any](f func(k func(A) R) R) Cont[R, A]

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

func Suspend[R, A any](f func(func(A) R) R) Cont[R, A]

Suspend creates a continuation from a CPS function. This is the primitive constructor for continuations that need direct access to the continuation.

func TellWriter

func TellWriter[W, B any](w W, next Cont[Resumed, B]) Cont[Resumed, B]

TellWriter fuses Tell + Then: performs Tell, then runs next.

func Then

func Then[R, A, B any](m Cont[R, A], n Cont[R, B]) Cont[R, B]

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

func ThrowError[E, A any](err E) Cont[Resumed, A]

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

type Eff[A any] = Cont[Resumed, A]

Eff is an effectful computation that produces a value of type A. This is the most common continuation type in effectful code.

func Pure added in v0.1.1

func Pure[A any](a A) Eff[A]

Pure lifts a value into an effectful computation with no effects. Pure(a) is equivalent to Return[Resumed](a) with full type inference on A.

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

func EvalStateError[S, E, A any](initial S, m Cont[Resumed, A]) Either[E, A]

EvalStateError runs a State+Error computation and returns only the Either result.

func FlatMapEither

func FlatMapEither[E, A, B any](e Either[E, A], f func(A) Either[E, B]) Either[E, B]

FlatMapEither sequences two Either computations.

func Left

func Left[E, A any](e E) Either[E, A]

Left creates a Left (error) value.

func MapEither

func MapEither[E, A, B any](e Either[E, A], f func(A) B) Either[E, B]

MapEither applies a function to the Right value.

func MapLeftEither

func MapLeftEither[E, F, A any](e Either[E, A], f func(E) F) Either[F, A]

MapLeftEither applies a function to the Left value.

func Right[E, A any](a A) Either[E, A]

Right creates a Right (success) value.

func RunError

func RunError[E, A any](m Cont[Resumed, A]) Either[E, A]

RunError runs an error-capable computation and returns Either.

func RunErrorExpr

func RunErrorExpr[E, A any](m Expr[A]) Either[E, A]

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

func RunReaderStateErrorExpr[Env, S, Err, A any](env Env, initial S, m Expr[A]) (Either[Err, A], S)

RunReaderStateErrorExpr runs an Expr with Reader, State, and Error effects. Handles Throw and Catch. Catch runs body with error-only handler internally.

func RunStateError

func RunStateError[S, E, A any](initial S, m Cont[Resumed, A]) (Either[E, A], S)

RunStateError runs a computation with both State and Error effects. Returns (Either[E, A], S) — state is always available, even on error.

func RunStateErrorExpr

func RunStateErrorExpr[S, E, A any](initial S, m Expr[A]) (Either[E, A], S)

RunStateErrorExpr runs an Expr with both State and Error effects. Handles Throw and Catch. Catch runs body with error-only handler internally.

func (Either[E, A]) GetLeft

func (e Either[E, A]) GetLeft() (E, bool)

GetLeft returns the Left value and true, or zero and false.

func (Either[E, A]) GetRight

func (e Either[E, A]) GetRight() (A, bool)

GetRight returns the Right value and true, or zero and false.

func (Either[E, A]) IsLeft

func (e Either[E, A]) IsLeft() bool

IsLeft returns true if this is a Left value.

func (Either[E, A]) IsRight

func (e Either[E, A]) IsRight() bool

IsRight returns true if this is a Right value.

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

type ErrorContext[E any] struct {
	Err    E
	HasErr bool
}

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 ExprBind

func ExprBind[A, B any](m Expr[A], f func(A) Expr[B]) Expr[B]

ExprBind creates a bind frame linking computation m to function f.

func ExprMap

func ExprMap[A, B any](m Expr[A], f func(A) B) Expr[B]

ExprMap creates a map frame transforming computation m with function f.

func ExprPerform

func ExprPerform[O Op[O, A], A any](op O) Expr[A]

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

func ExprReturn[A any](a A) Expr[A]

ExprReturn creates a completed computation with the given value.

func ExprSuspend

func ExprSuspend[A any](frame Frame) Expr[A]

ExprSuspend creates a computation suspended at the given frame.

func ExprThen

func ExprThen[A, B any](m Expr[A], n Expr[B]) Expr[B]

ExprThen creates a then frame sequencing m before n (discarding m's result).

func ExprThrowError

func ExprThrowError[E, A any](err E) Expr[A]

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

func Reify[A any](m Cont[Resumed, A]) Expr[A]

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

func ChainFrames(first, second Frame) Frame

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

func (Get[S]) DispatchState(state *S) (Resumed, bool)

DispatchState handles Get in State handler dispatch.

func (Get[S]) OpResult

func (Get[S]) OpResult() S

type Handler

type Handler[H Handler[H, R], R any] interface {
	Dispatch(op Operation) (Resumed, bool)
}

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

type Listen[W, A any] struct{ Body Cont[Resumed, A] }

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.

func (Listen[W, A]) OpResult

func (Listen[W, A]) OpResult() Pair[A, []W]

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)

func (*MapFrame[A, B]) Unwind

func (f *MapFrame[A, B]) Unwind(current Erased) (Erased, Frame)

Unwind performs a single step of reduction for the MapFrame.

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

func (o Modify[S]) DispatchState(state *S) (Resumed, bool)

DispatchState handles Modify in State handler dispatch.

func (Modify[S]) OpResult

func (Modify[S]) OpResult() S

type Op

type Op[O Op[O, A], A any] interface {
	OpResult() A // phantom type marker for result
}

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 Pair

type Pair[A, B any] struct {
	Fst A
	Snd B
}

Pair holds two values.

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

func (Phantom[A]) OpResult added in v0.1.1

func (Phantom[A]) OpResult() A

OpResult implements the phantom type marker for Op.

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

func (o Put[S]) DispatchState(state *S) (Resumed, bool)

DispatchState handles Put in State handler dispatch.

func (Put[S]) OpResult

func (Put[S]) OpResult() struct{}

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.

func (Tell[W]) OpResult

func (Tell[W]) OpResult() struct{}

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

func AcquireThenFrame() *ThenFrame[Erased, Erased]

AcquireThenFrame acquires a pooled single-use ThenFrame[Erased, Erased] whose Second and Next fields must be filled before evaluation.

func (*ThenFrame[A, B]) Unwind

func (f *ThenFrame[A, B]) Unwind(current Erased) (Erased, Frame)

Unwind performs a single step of reduction for the ThenFrame.

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.

func (Throw[E]) OpResult

func (Throw[E]) OpResult() Resumed

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL