# Either

> A value or an error, as a return type instead of an exception.

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

`Either[T, E]` is `Ok(value)` or `Err(error)`, success type first. There are no `Left` or `Right` aliases.

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

`map` runs on `Ok` and skips `Err`. `flat_map` takes a function that returns an `Either`.

```python
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 fail
parse_port("nope").or_else(lambda: parse_port("8080"))          # ignores the error
Ok(2).bimap(str.upper, lambda n: n * 10)                        # Ok(20); error function first
Ok(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:

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

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

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

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

```python
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`](https://docs.pyrula.io/primitives/validated.md#traversal-and-sequencing).

## 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`](https://docs.pyrula.io/primitives/option.md). [`@do`](https://docs.pyrula.io/primitives/do-notation.md) chains dependent `Either` calls. The JSON form is in [JSON form](https://docs.pyrula.io/primitives/json-form.md).
