Either
Either[T, E] is Ok(value) or Err(error), success type first. There are no Left or Right aliases.
from pyrula import Either, Ok, Err
def parse_port(raw: str) -> Either[int, str]: if not raw.isascii() or not raw.isdigit(): return Either.err(f"not a number: {raw!r}") port = int(raw) return Either.ok(port) if 0 < port < 65536 else Either.err(f"out of range: {port}")Transform
Section titled “Transform”map runs on Ok and skips Err. flat_map takes a function that returns an Either.
def check_available(port: int) -> Either[int, str]: return Err("port occupied") if port == 8081 else Ok(port)
parse_port("8080").map(lambda p: p + 1) # Ok(8081)parse_port("nope").map(lambda p: p + 1) # Err('not a number: ...')parse_port("8080").flat_map(check_available) # Ok(8080)
parse_port("nope").map_err(str.upper) # Err('NOT A NUMBER: ...')parse_port("nope").recover(lambda err: 8080) # Ok(8080)parse_port("nope").recover_with(lambda err: parse_port("8080")) # fallback that can failparse_port("nope").or_else(lambda: parse_port("8080")) # ignores the errorOk(2).bimap(str.upper, lambda n: n * 10) # Ok(20); error function firstOk(Ok(2)).flatten() # Ok(2)flatten raises TypeError if the Ok does not hold an Either.
ensure(predicate, error) turns a rejected Ok into an Err. A callable error receives the value:
Ok(70000).ensure(lambda p: 0 < p < 65536, lambda p: f"out of range: {p}")# Err('out of range: 70000')tap and tap_err observe a case without changing it. Callback exceptions propagate.
Get a value out
Section titled “Get a value out”parse_port("nope").get_or_else(0) # 0
parse_port("99999").fold( lambda err: f"rejected: {err}", lambda port: f"using {port}",) # "rejected: out of range: 99999"unwrap_or_raise(ValueError) raises on Err.
get_or_else evaluates its argument eagerly. get_or_else_with(factory) calls the factory once, only on Err.
Matching
Section titled “Matching”match parse_port("8080"): case Ok(port): message = f"using {port}" case Err(error): message = f"rejected: {error}"
assert message == "using 8080"Traversal and sequencing
Section titled “Traversal and sequencing”traverse(values, fn) calls fn in input order and stops at the first Err. sequence(values) collects existing Either values. Both accept generators and return a list inside Ok.
Either.traverse(["8080", "9000"], parse_port) # Ok([8080, 9000])Either.traverse(["8080", "nope", "9000"], parse_port) # Err("not a number: 'nope'")Either.sequence([Ok(8080), Ok(9000)]) # Ok([8080, 9000])A callback that returns a non-Either raises TypeError; callback exceptions propagate. To collect every error, use Validated.traverse.
Constructors
Section titled “Constructors”| Call | Result |
|---|---|
Either.ok(v) / Either.err(e) | Ok / Err |
Either.try_of(fn) | Ok(fn()), or Err if fn raises an Exception |
Either.cond(test, ok, err) | Ok(ok) if test else Err(err) |
to_option() drops the error and returns an Option. @do chains dependent Either calls. The JSON form is in JSON form.