# Examples

> Runnable examples that combine Pyrula's core types.

Source: https://docs.pyrula.io/primitives/examples/

## Stop at the first error

[Do-notation](https://docs.pyrula.io/primitives/do-notation.md) reads like straight-line code and short-circuits on failure.

```python
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}")

@do
def 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 runs
```

## Collect every error

`Either.traverse` stops at the first error. `Validated.traverse` and `zip` check every input.

```python
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

```python
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

`Try.apply` captures an `Exception`; `to_either` carries it in `Err`.

```python
from pyrula import Try

result = Try.apply(lambda: int("42")).to_either()
# Ok(42), or Err(ValueError(...)) on bad input
```

## Never empty

```python
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([])              # Nothing
```

## Split a batch

```python
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

```python
from pyrula import Duration

deadline = Duration.seconds(0.5) * 10   # Duration(5s)
deadline.to_millis()                    # 5000
deadline.to_timedelta()                 # datetime.timedelta(seconds=5)
```
