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
  • Dynamic windwind context manager establishes before/after guards that are re-invoked on multi-shot re-entry, with optional auto-management of context managers returned by before
  • 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

Dynamic wind

The wind context manager establishes before/after guards around a dynamic extent. When a multi-shot continuation captured inside the with block is resumed, the before thunk is called again; when it exits, the after thunk runs.

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

choose: Effect[[], int] = effect("choose")
h: Handler[list[int]] = create_handler(choose)

@h.on(choose)
def _choose(k: Resume[int, list[int]]):
    return k(1) + k(2)

log: list[str] = []

def run() -> list[int]:
    with wind(lambda: log.append("before"), lambda: log.append("after")):
        return [choose() * 10]
    assert False, "unreachable"

result = h(run)
print(result)  # [10, 20]
print(log)     # ['before', 'after', 'before', 'after']

If before returns a context manager and auto_exit=True (the default), __enter__ and __exit__ are called automatically:

with wind(lambda: open("data.txt")) as ref:
    ref.unwrap().read()
# file is closed on exit

wind_range is a multi-shot-safe replacement for range() in for loops. Python's range() iterator is shared across shots and exhausted after the first; wind_range saves and restores the iterator position automatically:

with wind_range(n) as r:
    for i in r:
        v = choose()  # multi-shot safe

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)
wind(before, after, *, auto_exit=True) Dynamic wind context manager
wind_range(stop) / wind_range(start, stop, step) Multi-shot-safe range() for for loops
Ref[T] Reference wrapper returned by wind; call unwrap() to get the value

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.3.0.tar.gz (73.0 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.3.0-cp314-cp314-win_amd64.whl (77.8 kB view details)

Uploaded CPython 3.14Windows x86-64

aleff-0.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (104.6 kB view details)

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

aleff-0.3.0-cp314-cp314-macosx_11_0_arm64.whl (80.4 kB view details)

Uploaded CPython 3.14macOS 11.0+ ARM64

aleff-0.3.0-cp314-cp314-macosx_10_15_x86_64.whl (80.2 kB view details)

Uploaded CPython 3.14macOS 10.15+ x86-64

aleff-0.3.0-cp313-cp313-win_amd64.whl (77.3 kB view details)

Uploaded CPython 3.13Windows x86-64

aleff-0.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (102.6 kB view details)

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

aleff-0.3.0-cp313-cp313-macosx_11_0_arm64.whl (79.8 kB view details)

Uploaded CPython 3.13macOS 11.0+ ARM64

aleff-0.3.0-cp313-cp313-macosx_10_13_x86_64.whl (79.6 kB view details)

Uploaded CPython 3.13macOS 10.13+ x86-64

aleff-0.3.0-cp312-cp312-win_amd64.whl (77.3 kB view details)

Uploaded CPython 3.12Windows x86-64

aleff-0.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (95.7 kB view details)

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

aleff-0.3.0-cp312-cp312-macosx_11_0_arm64.whl (75.8 kB view details)

Uploaded CPython 3.12macOS 11.0+ ARM64

aleff-0.3.0-cp312-cp312-macosx_10_13_x86_64.whl (75.7 kB view details)

Uploaded CPython 3.12macOS 10.13+ x86-64

File details

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

File metadata

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

File hashes

Hashes for aleff-0.3.0.tar.gz
Algorithm Hash digest
SHA256 35f8cec114b70c03a3d024f24369542a002b2a33a91e59aec67c3414dcee0777
MD5 97f5431b57c53fff4f6f8f9dbb182c86
BLAKE2b-256 0b90ed21551d1cec030acf555267c90f7282daa9392d5142cbeaef41512f219e

See more details on using hashes here.

Provenance

The following attestation bundles were made for aleff-0.3.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.3.0-cp314-cp314-win_amd64.whl.

File metadata

  • Download URL: aleff-0.3.0-cp314-cp314-win_amd64.whl
  • Upload date:
  • Size: 77.8 kB
  • Tags: CPython 3.14, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for aleff-0.3.0-cp314-cp314-win_amd64.whl
Algorithm Hash digest
SHA256 9755bb580f51e7d44de8a4ec81637fb8baa54f427e6b2148e353559c26dad801
MD5 e2d84960c92d12bbfcb92fe2538d5584
BLAKE2b-256 36502e2082a3e50be526e5acc7c2702d9253631ce9827309fc1770f67c5130a4

See more details on using hashes here.

Provenance

The following attestation bundles were made for aleff-0.3.0-cp314-cp314-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.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for aleff-0.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 1488f02643a02195b68e8e5abae89fdf7267cc742765a21f1cc1d363720859d2
MD5 d878bc405c6d04730465956020d1f885
BLAKE2b-256 4bb58adb4710d0d5f2eef390e028643c7ad8825a55d98390f787ae876303bb7d

See more details on using hashes here.

Provenance

The following attestation bundles were made for aleff-0.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_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.3.0-cp314-cp314-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for aleff-0.3.0-cp314-cp314-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 c78551a644a9d82a1f230038f861be7c24b132291f1e1d223052d03719d46d75
MD5 412b5cd757b9fa7e1d46a418fd88a365
BLAKE2b-256 715541c688c389073130fe6942b56c858dbe5d7dafefea4880513d6c744edd21

See more details on using hashes here.

Provenance

The following attestation bundles were made for aleff-0.3.0-cp314-cp314-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.3.0-cp314-cp314-macosx_10_15_x86_64.whl.

File metadata

File hashes

Hashes for aleff-0.3.0-cp314-cp314-macosx_10_15_x86_64.whl
Algorithm Hash digest
SHA256 185a3e18ca63a1096849cd037bafe61fc44a1996e9c35cf7f619d70f38217eef
MD5 f9c1e7c15ba7948df601d42efe88153e
BLAKE2b-256 fa338c8af1f68c5c93a727dd3fb87098cfdf708021f5854c643044804aa8cb9b

See more details on using hashes here.

Provenance

The following attestation bundles were made for aleff-0.3.0-cp314-cp314-macosx_10_15_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.3.0-cp313-cp313-win_amd64.whl.

File metadata

  • Download URL: aleff-0.3.0-cp313-cp313-win_amd64.whl
  • Upload date:
  • Size: 77.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.3.0-cp313-cp313-win_amd64.whl
Algorithm Hash digest
SHA256 af9d770bda75c12edca76261f73bbb523311818f5a6e3ca4811848ca3e407797
MD5 1c69d956055282adc79c233be261878a
BLAKE2b-256 03b10e5208e9f836b5ef906bc264d3f914efc3c6bf5fc8093c9c8d8f8f3ad631

See more details on using hashes here.

Provenance

The following attestation bundles were made for aleff-0.3.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.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for aleff-0.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 e5ff2c50e5da8871a08b24d0223a3eef93d9cce2fb39c0da95e53218288fbaba
MD5 0550b958a37778576eb837a27d0a3da8
BLAKE2b-256 53f68a8ecaa169a9ae89d449d1a08c143ffe549b82e5abff4de9497607dd4bbc

See more details on using hashes here.

Provenance

The following attestation bundles were made for aleff-0.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_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.3.0-cp313-cp313-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for aleff-0.3.0-cp313-cp313-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 e53b5b9849f1abfa1530e6362bc92cbc512fa2d5b9889941377c57118e6226f2
MD5 7259bc717714112af5fbcf2961c53648
BLAKE2b-256 8e80a83f5acd8ef6c59cb85fb7fdea428510466a9666ce58ccf680f0ee35468e

See more details on using hashes here.

Provenance

The following attestation bundles were made for aleff-0.3.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.3.0-cp313-cp313-macosx_10_13_x86_64.whl.

File metadata

File hashes

Hashes for aleff-0.3.0-cp313-cp313-macosx_10_13_x86_64.whl
Algorithm Hash digest
SHA256 761dfd5440402ec172426042da95bcf628ada67caa18800b3c564b66c376b547
MD5 e5b54d7fad63eb67fbb6811c467f4df2
BLAKE2b-256 327b956e9bca0c5c6f4f3101e42889c365ba22a379a8b918525b1f8515389d12

See more details on using hashes here.

Provenance

The following attestation bundles were made for aleff-0.3.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.3.0-cp312-cp312-win_amd64.whl.

File metadata

  • Download URL: aleff-0.3.0-cp312-cp312-win_amd64.whl
  • Upload date:
  • Size: 77.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.3.0-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 485191939041de0135d3f0e48185cac6f44bed60284b4f949ab09fde447a03cd
MD5 7314ad7744d1c4b3f542a9febf328862
BLAKE2b-256 3593a1e1a9a383e0212fa96a277b61c3865affb4be5e0bcc069fb7c13691ceec

See more details on using hashes here.

Provenance

The following attestation bundles were made for aleff-0.3.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.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for aleff-0.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 5d85acf6afee11d9b3edbd9d721ba5761fb20ade66edc34aea94ce7128e0042c
MD5 11177e0f58149204598bc203c3c24b3f
BLAKE2b-256 d6841be18b4eef27b3e9215b0a5079b6ba4bf1ccf7485cf2ea004566ee9fe3ed

See more details on using hashes here.

Provenance

The following attestation bundles were made for aleff-0.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_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.3.0-cp312-cp312-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for aleff-0.3.0-cp312-cp312-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 addb7a9721d6dc47ccadb492b9c35ee7f7cbb7b4f59153b42c52f198f8395ba5
MD5 104405072eced31738bfc70237dcd55d
BLAKE2b-256 fed8b94285a309b873fa73bc763ad1f99f7aef8632de2540bdc076b94d5483e0

See more details on using hashes here.

Provenance

The following attestation bundles were made for aleff-0.3.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.3.0-cp312-cp312-macosx_10_13_x86_64.whl.

File metadata

File hashes

Hashes for aleff-0.3.0-cp312-cp312-macosx_10_13_x86_64.whl
Algorithm Hash digest
SHA256 971d5cfba768e903992f66603c9b38021c5e18126f03a882b0484058f02f8e36
MD5 bfdad586a652e7477b7b8bf9632d40c7
BLAKE2b-256 779d78f3e10ee5579c1efbe7a00d318b219eea1f59fee8cd02150d57daef08af

See more details on using hashes here.

Provenance

The following attestation bundles were made for aleff-0.3.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