# Try

> Run something that might throw and get the outcome back as a value.

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

`Try[T]` is `Success(value)` or `Failure(exc)`. It moves an exception from control flow into a value.

```python
from pyrula import Try, Success, Failure

Try.apply(lambda: int("42"))     # Success(42)
Try.apply(lambda: int("nope"))   # Failure(ValueError(...))
```

## Work with the outcome

```python
result = Try.apply(lambda: int("42"))

result.map(lambda n: n * 2).get_or_else(0)
result.fold(lambda exc: f"bad input: {exc}", lambda n: f"got {n}")
result.recover(lambda exc: 0)   # Failure becomes Success(0)
```

`recover_with(fn)` recovers with another `Try`; `or_else(fn)` swaps in a fallback `Try`. `get_or_else` evaluates its argument eagerly; `get_or_else_with(factory)` calls the factory once, only on `Failure`.

`to_either()` gives `Ok(value)` or `Err(exception)`. Chain `.map_err(...)` on the [`Either`](https://docs.pyrula.io/primitives/either.md) to turn the exception into an application error (`Try` itself has no `map_err`).

## Exception boundaries

`Try.apply`, `Try.traverse`, `map` and `flat_map` capture `Exception`. `KeyboardInterrupt` and `SystemExit` propagate. `tap` and `tap_failure` are observers: their exceptions propagate instead of becoming a `Failure`.

```python
def broken(value: int) -> int:
    raise ValueError("callback failed")

assert isinstance(Success(1).map(broken), Failure)

try:
    Success(1).tap(broken)
except ValueError:
    pass   # observer errors are not captured
```

## Traversal and sequencing

`traverse(values, fn)` stops at the first `Failure`; `sequence(values)` collects existing `Try` values. Both return a `list` inside `Success`.

```python
def parse(raw: str) -> Try[int]:
    return Try.apply(lambda: int(raw))

Try.traverse(["1", "2"], parse)          # Success([1, 2])
Try.traverse(["1", "nope", "3"], parse)  # Failure(ValueError(...))
Try.sequence([Success(1), Success(2)])   # Success([1, 2])
```

`traverse` captures `Exception` from iteration or the callback as a `Failure`. A callback that returns a non-`Try` raises `TypeError`.

## Matching and flattening

```python
match Try.apply(lambda: int("42")):
    case Success(value):
        message = f"parsed: {value}"
    case Failure(error):
        message = f"rejected: {error}"

assert message == "parsed: 42"
Success(Success(5)).flatten()   # Success(5)
```

`flatten` raises `TypeError` if the `Success` does not hold a `Try`.

[`@do`](https://docs.pyrula.io/primitives/do-notation.md) chains dependent `Try` calls. The JSON form is in [JSON form](https://docs.pyrula.io/primitives/json-form.md).
