Pragmatic do-notation and effects system for Python
Project description
doeff - Algebraic Effects for Python
doeff is an algebraic effects runtime for Python with one-shot continuations and a Rust VM.
Programs are written with generator-based @do notation and executed through explicit handler stacks.
Installation
pip install doeff
Quick Start
from doeff import do, run, WithHandler
from doeff_core_effects import Ask, Get, Put, Tell
from doeff_core_effects.handlers import reader, state, writer
from doeff_core_effects.scheduler import scheduled
@do
def counter_program():
yield Put("counter", 0)
yield Tell("Starting computation")
count = yield Get("counter")
yield Put("counter", count + 1)
return count + 1
# Compose handlers explicitly with WithHandler
prog = counter_program()
prog = WithHandler(writer(), prog)
prog = WithHandler(state(), prog)
prog = WithHandler(reader(env={"greeting": "hello"}), prog)
result = run(scheduled(prog))
print(result) # 1
Runtime API
| Entrypoint | Signature | Use case |
|---|---|---|
run |
run(doexpr) |
Execute a DoExpr program to completion |
run() takes a single DoExpr (program node) and returns the result value directly.
Handlers are composed explicitly using WithHandler(handler, body).
Use scheduled(prog) to wrap with the scheduler for concurrency effects.
Handler Composition
Handlers are installed with WithHandler(handler, body). Stack multiple handlers by nesting:
from doeff import do, run, WithHandler, Resume, Pass
from doeff_core_effects import Ask
@do
def my_handler(effect, k):
if isinstance(effect, Ask):
return (yield Resume(k, "hello"))
yield Pass(effect, k)
@do
def prog():
return (yield Ask("greeting"))
result = run(WithHandler(my_handler, prog()))
print(result) # hello
Handlers are @do functions that receive (effect, k). They can:
yield Resume(k, value)— resume the continuation with a valueyield Pass(effect, k)— forward the effect to outer handlersyield Transfer(k, value)— tail-resume (handler done after this)
Effect Surface
Core effects (from doeff_core_effects):
- Reader:
Ask(key),Local(env, program) - State:
Get(key),Put(key, value) - Writer:
Tell(message),slog(msg, **kwargs) - Error:
Try(program)— returnsOk(value)orErr(error) - Observe:
Listen(program, types=...)— collect effects during execution
Scheduler effects (from doeff_core_effects.scheduler):
- Concurrency:
Spawn(program),Wait(task),Gather(*tasks),Race(*tasks) - Promises:
CreatePromise(),CompletePromise(p, v),FailPromise(p, e) - External:
CreateExternalPromise()— bridge external threads - Semaphores:
CreateSemaphore(n),AcquireSemaphore(s),ReleaseSemaphore(s)
Handlers
Built-in handlers (from doeff_core_effects.handlers):
| Handler | Factory | Effects handled |
|---|---|---|
| Reader | reader(env={...}) |
Ask |
| Lazy Ask | lazy_ask(env={...}) |
Ask, Local (with caching) |
| State | state(initial={...}) |
Get, Put |
| Writer | writer() |
Tell / WriterTellEffect |
| Try | try_handler |
Try |
| Slog | slog_handler() |
Slog |
| Local | local_handler |
Local |
| Listen | listen_handler |
Listen |
| Await | await_handler() |
Await |
| Scheduler | scheduled(prog) |
Spawn, Wait, Gather, Race, etc. |
Scheduler and Concurrency
from doeff import do, run, WithHandler
from doeff_core_effects import Tell
from doeff_core_effects.handlers import writer
from doeff_core_effects.scheduler import scheduled, Spawn, Wait, Gather
@do
def worker(label):
yield Tell(f"working: {label}")
return label
@do
def main():
t1 = yield Spawn(worker("a"))
t2 = yield Spawn(worker("b"))
results = yield Gather(t1, t2)
return results
result = run(scheduled(WithHandler(writer(), main())))
print(result) # ['a', 'b']
CLI
# Run a program with auto-discovered interpreter
doeff run --program myapp.module.program
# With explicit interpreter
doeff run --program myapp.program --interpreter myapp.interpreter
# With environment
doeff run --program myapp.program --env myapp.default_env
# Inline code
doeff run -c 'return 42'
# Apply transform (T -> Program[U])
doeff run --program myapp.program --apply myapp.transforms.wrap
# JSON output
doeff run --program myapp.program --format json
The CLI supports automatic interpreter/environment discovery via doeff-indexer.
See docs/14-cli-auto-discovery.md for marker syntax and hierarchy rules.
Development
uv sync --reinstall # rebuild Rust VM
uv run pytest
License
MIT License. See LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file doeff-0.4.0.tar.gz.
File metadata
- Download URL: doeff-0.4.0.tar.gz
- Upload date:
- Size: 105.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
95f1dabc752ebeb9f38a163494a3c920b1339fcefa882fcc51332c4b2739e7fa
|
|
| MD5 |
b3583a5d0a7eb9ded7c391118e9d57fb
|
|
| BLAKE2b-256 |
1988e7b791de2ebfb3590f64e4a4616a0ce228d41e8f2a57c184ad7345e4a4d5
|
File details
Details for the file doeff-0.4.0-py3-none-any.whl.
File metadata
- Download URL: doeff-0.4.0-py3-none-any.whl
- Upload date:
- Size: 21.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e511d231ddcb91330cb5514c916bcc6f2ac8078ab3d3043f7deaf578a20aa0fa
|
|
| MD5 |
109b033776778af8d713dfe9b1620cb0
|
|
| BLAKE2b-256 |
0dfcacff069e48387a1abdf1f6dce8f53e02e1cacea7f56497e928726a1c5f22
|