Examples
Stop at the first error
Section titled “Stop at the first error”Do-notation reads like straight-line code and short-circuits on failure.
from pyrula import Ok, Err, do
def parse_int(s: str): try: return Ok(int(s)) except ValueError: return Err(f"not an int: {s!r}")
@dodef total(a: str, b: str): x = yield parse_int(a) y = yield parse_int(b) return x + y
total("2", "3") # Ok(5)total("x", "3") # Err("not an int: 'x'"); second parse never runsCollect every error
Section titled “Collect every error”Either.traverse stops at the first error. Validated.traverse and zip check every input.
from pyrula import Either, Err, Invalid, Ok, Valid, Validated
def parse_number(raw: str) -> Either[int, str]: return Ok(int(raw)) if raw.isascii() and raw.isdigit() else Err(f"not an int: {raw!r}")
def validate_number(raw: str) -> Validated[int, str]: return Validated.from_either(parse_number(raw))
inputs = ["1", "bad", "3", "also bad"]assert Either.traverse(inputs, parse_number) == Err("not an int: 'bad'")assert Validated.traverse(inputs, validate_number) == Invalid([ "not an int: 'bad'", "not an int: 'also bad'",])assert Either.traverse(["1", "3"], parse_number) == Ok([1, 3])Config without None checks
Section titled “Config without None checks”from pyrula import Option
cfg = {"timeout": "30"}
timeout = ( Option.of(cfg.get("timeout")) # Some("30") or Nothing .map(int) .filter(lambda s: s > 0) .get_or_else(10) # 30; 10 if missing or non-positive)Exceptions as values
Section titled “Exceptions as values”Try.apply captures an Exception; to_either carries it in Err.
from pyrula import Try
result = Try.apply(lambda: int("42")).to_either()# Ok(42), or Err(ValueError(...)) on bad inputNever empty
Section titled “Never empty”from pyrula import NonEmptyList
scores = NonEmptyList(5, 3, 8)scores.reduce(lambda a, b: max(a, b)) # 8, no seed value needed
NonEmptyList.from_list([]) # NothingSplit a batch
Section titled “Split a batch”from pyrula import IList, Ok, Err
def parse_int(s: str): try: return Ok(int(s)) except ValueError: return Err(f"bad: {s!r}")
results = IList(["1", "two", "3", "four"]).map(parse_int)ok_vals, errors = results.partition_either()# ok_vals: IList([1, 3])# errors: IList(["bad: 'two'", "bad: 'four'"])Timeouts as values
Section titled “Timeouts as values”from pyrula import Duration
deadline = Duration.seconds(0.5) * 10 # Duration(5s)deadline.to_millis() # 5000deadline.to_timedelta() # datetime.timedelta(seconds=5)