Skip to content
Pyrula

Do Notation

Long flat_map chains get hard to read. @do lets you write them as a generator: yield a wrapped value to pull the inner value out, and the first failure short-circuits the whole block.

from pyrula import do, Ok, Err
@do
def total(a: str, b: str):
x = yield parse_int(a) # parse_int returns Ok/Err
y = yield parse_int(b)
return x + y
total("2", "3") # Ok(5)
total("2", "nope") # Err(...), and the return never runs

Each yield unwraps an Ok and binds the value, or stops at the first Err and returns it. The body reads like straight-line code while keeping the error handling.

It works the same for Option and Try:

@do
def lookup():
user = yield find_user(uid) # Some/Nothing
email = yield user.contact_email # Some/Nothing
return email

Two rules:

  • Stick to one family per block. Mixing Ok and Some in the same @do raises a TypeError.
  • Use @do_async for async def blocks. Same semantics, awaits where needed.
from pyrula import do_async
@do_async
async def handler():
user = yield await fetch_user(uid)
return user.name

@do is Scala’s for-comprehension. x = yield e is x <- e and return is yield, with the first failure short-circuiting, exactly as a for { ... } yield over Either/Option/Try desugars to flatMap and map.