Skip to main content

Algebraic effects for Python — deep and shallow, stateful, composable, multi-shot handlers

Project description

aleff

Algebraic effects for Python — deep and shallow, stateful, composable, multi-shot handlers.

Features

  • Deep handlers — effects propagate through nested function calls without annotation
  • Shallow handlers — handle an effect once, then delegate re-installation to the handler function; enables state machines, shift0/reset0, and strategy changes between invocations
  • Stateful handlers — handler functions can execute code after resume, enabling patterns like transactions and reverse-mode AD
  • Multi-shot continuationsresume can be called multiple times in a single handler, enabling backtracking search, non-determinism, and other advanced patterns
  • Sync and async — both synchronous (Handler) and asynchronous (AsyncHandler) handlers are supported, with transparent bridging between the two
  • Effect composition — handler functions can perform effects themselves, dispatched to enclosing handlers; enables layered architectures and modular effect stacking
  • Effect annotation@effect(step1, step2) collects effect sets transitively from decorated functions
  • Introspectioneffects(fn) and unhandled_effects(fn, h) for querying and validating effect coverage
  • Typed — effect parameters and return types are checked by type checkers (pyright, ty)
  • No macros, no code generation — pure Python library built on greenlet and a small CPython C extension

Requirements

  • CPython >=3.12
    • Tested versions:
      • 3.12.13
      • 3.13.12
      • 3.14.3
      • 3.14.3t (free-threaded)
  • greenlet >= 3.3.2
  • Linux / macOS / Windows

Installation

# uv
uv add aleff

# pip
pip install aleff

Quick start

from aleff import effect, Effect, Resume, create_handler

# Define effects
read: Effect[[], str] = effect("read")
write: Effect[[str], int] = effect("write")

# Write business logic using effects
def run():
    s = read()
    return write(s)

# Provide handler implementations
h = create_handler(read, write)

@h.on(read)
def _read(k: Resume[str, int]):
    return k("file contents")

@h.on(write)
def _write(k: Resume[int, int], contents: str):
    print(f"writing: {contents}")
    return k(len(contents))

result = h(run)
print(result)  # 13

Multi-shot example

from aleff import effect, Effect, Handler, Resume, create_handler

choose: Effect[[int], int] = effect("choose")

h: Handler[list[int]] = create_handler(choose)

@h.on(choose)
def _choose(k: Resume[int, list[int]], n: int):
    # Resume once for each choice and collect all results
    results: list[int] = []
    for i in range(n):
        results += k(i)
    return results

def computation():
    x = choose(3)  # 0, 1, or 2
    y = choose(2)  # 0 or 1
    return [x * 10 + y]

result = h(computation)
print(result)  # [0, 1, 10, 11, 20, 21]

Effect composition

Handler functions can perform effects that are handled by enclosing handlers:

from aleff import effect, Effect, Resume, create_handler

log: Effect[[str], None] = effect("log")
parse: Effect[[str], int] = effect("parse")

# Outer handler: logging
h_log = create_handler(log)

@h_log.on(log)
def _log(k: Resume[None, int], msg: str):
    print(f"[LOG] {msg}")
    return k(None)

# Inner handler: parsing with logging
h_parse = create_handler(parse)

@h_parse.on(parse)
def _parse(k: Resume[int, int], s: str):
    log(f"parsing: {s}")       # handled by the outer handler
    return k(int(s))

result = h_log(lambda: h_parse(lambda: parse("42") + 1))
# prints: [LOG] parsing: 42
print(result)  # 43

How it works

Effects are declared as typed values and invoked like regular function calls. A Handler intercepts these calls via greenlet-based context switching:

  1. Business logic runs in a greenlet
  2. When an effect is invoked, control switches to the handler
  3. The handler processes the effect and calls resume(value) to return a value
  4. If resume is called multiple times, each call restores a snapshot of the continuation's frames (multi-shot)
  5. If the handler returns without calling resume, the computation is aborted (early exit)

Because handlers use greenlets (not exceptions), the control flow is:

  • Transparent — no yield, await, or special syntax in business logic
  • Stateful — code after resume runs after the rest of the computation completes, enabling reverse-order execution (useful for backpropagation, transactions, etc.)

Multi-shot continuations are implemented via a CPython C extension (aleff._multishot.v1._aleff) that snapshots and restores interpreter frame chains.

Package structure

Package Description
aleff Default: re-exports aleff.multishot (multi-shot handlers)
aleff.multishot Multi-shot handlers with frame snapshot/restore
aleff.oneshot One-shot handlers (no C extension required)

Examples

See examples/ for demonstrations:

  • N-Queens — backtracking search via multi-shot continuations
  • Amb / Logic puzzle — Scheme-style amb operator and constraint solving (SICP Exercise 4.42)
  • Probability — exact discrete probability distributions via weighted multi-shot
  • Dependency injection — swap DB/email/logging implementations
  • Record/Replay — record effect results, replay without side effects
  • Transactions — buffer writes, commit on success, rollback on failure
  • Automatic differentiation — forward-mode (dual numbers) and reverse-mode (backpropagation) with the same math expressions
  • Shallow state machine — mutable state (get/put) and traffic light controller via shallow handler re-installation
  • shift/reset, shift0/reset0 — delimited continuations: deep = shift/reset, shallow = shift0/reset0, with generator example

API reference

Function / Class Description
effect("name") Create a new Effect
@effect(e1, e2, ...) Decorate a function to declare its effects
create_handler(*effects, shallow=False) Create a synchronous handler
create_async_handler(*effects, shallow=False) Create an asynchronous handler
h.on(effect) Register a handler function (decorator)
h(caller) Run caller with the handler active
effects(fn) Get the declared effect set of a function
unhandled_effects(fn, *handlers) Get effects not covered by the given handlers
Effect[P, R] Effect protocol (parameters P, return type R)
Handler[V] Sync handler protocol
AsyncHandler[V] Async handler protocol
Resume[R, V] Sync continuation (k(value) -> V)
ResumeAsync[R, V] Async continuation (await k(value) -> V)

Development

From source:

git clone https://github.com/hnmr293/aleff.git
cd aleff
uv sync
# Run tests
uv run pytest

# Run tests on all supported Python versions
./run_tests.sh

# Format
uv run ruff format

# Lint
uv run pyright

License

Apache-2.0

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

aleff-0.2.0.tar.gz (60.9 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

aleff-0.2.0-cp313-cp313-win_amd64.whl (72.3 kB view details)

Uploaded CPython 3.13Windows x86-64

aleff-0.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl (97.7 kB view details)

Uploaded CPython 3.13manylinux: glibc 2.17+ x86-64manylinux: glibc 2.28+ x86-64

aleff-0.2.0-cp313-cp313-macosx_11_0_arm64.whl (74.9 kB view details)

Uploaded CPython 3.13macOS 11.0+ ARM64

aleff-0.2.0-cp313-cp313-macosx_10_13_x86_64.whl (74.6 kB view details)

Uploaded CPython 3.13macOS 10.13+ x86-64

aleff-0.2.0-cp312-cp312-win_amd64.whl (72.3 kB view details)

Uploaded CPython 3.12Windows x86-64

aleff-0.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl (90.8 kB view details)

Uploaded CPython 3.12manylinux: glibc 2.17+ x86-64manylinux: glibc 2.28+ x86-64

aleff-0.2.0-cp312-cp312-macosx_11_0_arm64.whl (70.9 kB view details)

Uploaded CPython 3.12macOS 11.0+ ARM64

aleff-0.2.0-cp312-cp312-macosx_10_13_x86_64.whl (70.7 kB view details)

Uploaded CPython 3.12macOS 10.13+ x86-64

File details

Details for the file aleff-0.2.0.tar.gz.

File metadata

  • Download URL: aleff-0.2.0.tar.gz
  • Upload date:
  • Size: 60.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for aleff-0.2.0.tar.gz
Algorithm Hash digest
SHA256 cbf81fc5bc7f68db73eb76d711f9eab58d40dd831d6bf03723272ad81e6835be
MD5 3d5300d7f93656b865ac25f26ee21aa0
BLAKE2b-256 0b89766b234febd1a750036f4ffee0937976b85ecfc540d79386df7498714350

See more details on using hashes here.

Provenance

The following attestation bundles were made for aleff-0.2.0.tar.gz:

Publisher: publish.yml on hnmr293/aleff

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file aleff-0.2.0-cp313-cp313-win_amd64.whl.

File metadata

  • Download URL: aleff-0.2.0-cp313-cp313-win_amd64.whl
  • Upload date:
  • Size: 72.3 kB
  • Tags: CPython 3.13, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for aleff-0.2.0-cp313-cp313-win_amd64.whl
Algorithm Hash digest
SHA256 963bd1bdb17c66340be25e315a078d05382d3baca8d26a40d8df93e944fd1c0e
MD5 fcc9da2005b8a7f9b7f98e7913f29afe
BLAKE2b-256 bb50f7c9227eb5fef3f409be72f9e765cbc4fad7219917f419f743941922eba7

See more details on using hashes here.

Provenance

The following attestation bundles were made for aleff-0.2.0-cp313-cp313-win_amd64.whl:

Publisher: publish.yml on hnmr293/aleff

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file aleff-0.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for aleff-0.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 cbde5e7f44db6122b7d682d8691e29cd3103e4fefa5cce7375adb056deb8f954
MD5 532ea542e0ed7d8b38f38871d0140553
BLAKE2b-256 ad40af8454b2bd9f6253abe6d35f746bba5cd3c43a1958e69dc6c03535decbd2

See more details on using hashes here.

Provenance

The following attestation bundles were made for aleff-0.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl:

Publisher: publish.yml on hnmr293/aleff

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file aleff-0.2.0-cp313-cp313-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for aleff-0.2.0-cp313-cp313-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 2a7d7fa3c231be3a67f51d243b2e99f231a40ba96dcc423b74941616d716f0ac
MD5 4bc60825c3b62bb9b07070029ee33a06
BLAKE2b-256 4c0c5a0c3637e9d0466dd2bd6dc7ca75da6bc92faf37fbf8fead0237eb590162

See more details on using hashes here.

Provenance

The following attestation bundles were made for aleff-0.2.0-cp313-cp313-macosx_11_0_arm64.whl:

Publisher: publish.yml on hnmr293/aleff

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file aleff-0.2.0-cp313-cp313-macosx_10_13_x86_64.whl.

File metadata

File hashes

Hashes for aleff-0.2.0-cp313-cp313-macosx_10_13_x86_64.whl
Algorithm Hash digest
SHA256 efa6301f5bb843fecd751fa0fcee7412d64823dbb5aa90867f9bb09a26d0bc18
MD5 e0c38186d90206262a313f6c02db9636
BLAKE2b-256 7cc914b3dcd12f44236ab75e49f6ed5d69fb7409a8cf3c8942b149e288650456

See more details on using hashes here.

Provenance

The following attestation bundles were made for aleff-0.2.0-cp313-cp313-macosx_10_13_x86_64.whl:

Publisher: publish.yml on hnmr293/aleff

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file aleff-0.2.0-cp312-cp312-win_amd64.whl.

File metadata

  • Download URL: aleff-0.2.0-cp312-cp312-win_amd64.whl
  • Upload date:
  • Size: 72.3 kB
  • Tags: CPython 3.12, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for aleff-0.2.0-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 1be35088fc68d1da22b69c52fdb333b2642e970f2228bdb08e3c2d19631da2cc
MD5 1fbec8e89554530ea21f36ef309f6f65
BLAKE2b-256 5a633d7101d443696cb3f0be1f147a89a6cf6596ce858cdcab59d36e229058c2

See more details on using hashes here.

Provenance

The following attestation bundles were made for aleff-0.2.0-cp312-cp312-win_amd64.whl:

Publisher: publish.yml on hnmr293/aleff

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file aleff-0.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for aleff-0.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 441ae704887748a6167a801d626a56ac73cad2eec831bfb080376419be84e2ef
MD5 74ca2f5104f24ca6da85d15bd601a341
BLAKE2b-256 82dbd07313515771cba88e5ecbd07b0fbf6c9cb0c41845773ee89fcd63662e7f

See more details on using hashes here.

Provenance

The following attestation bundles were made for aleff-0.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl:

Publisher: publish.yml on hnmr293/aleff

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file aleff-0.2.0-cp312-cp312-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for aleff-0.2.0-cp312-cp312-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 ade9b865527f8908303fca1df3cb065aff54cace1e3a93402320d33d77a0711d
MD5 7c69925f253581f81189dc627b0d27e2
BLAKE2b-256 0ae6f34a21e3eec245b7ae0664dcda794b5146c66e673c2ff10449e3d0d64ae4

See more details on using hashes here.

Provenance

The following attestation bundles were made for aleff-0.2.0-cp312-cp312-macosx_11_0_arm64.whl:

Publisher: publish.yml on hnmr293/aleff

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file aleff-0.2.0-cp312-cp312-macosx_10_13_x86_64.whl.

File metadata

File hashes

Hashes for aleff-0.2.0-cp312-cp312-macosx_10_13_x86_64.whl
Algorithm Hash digest
SHA256 5f153152f85e0106040ca7b193890306ab0db55e32b8cabc3c070af5a9375c7f
MD5 74da23fe159eea820b515614ffc7e6ab
BLAKE2b-256 2c4eb1f59e97aec57aeb6d86505c301359e49f86180da59d78701ab58d8f206e

See more details on using hashes here.

Provenance

The following attestation bundles were made for aleff-0.2.0-cp312-cp312-macosx_10_13_x86_64.whl:

Publisher: publish.yml on hnmr293/aleff

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page