# Accord

Accord brings the robust error handling and optional value patterns from Rust's `Result` and `Option` types to Python. This library provides a type-safe way to handle operations that can fail or may not have a value, leading to more predictable and maintainable code.

We intend not to use any kind of non-std library on Accord, so we do not need any extra dependency as a drawback for using it.

## Why "Accord"?

> The tale I weave for you now is about a puppet that travels the world.
>
> The puppet's goal is to observe how humans live their lives.
>
> This puppet continues its journey still, carrying its large suitcase with it...

## Features

- **`Result<T, E>`**: Represents either a success (`Ok(T)`) or a failure (`Err(E)`).
- **`Option<T>`**: Represents an optional value, being either `Some(T)` or `None`.
- **Ergonomic API**: Methods like `map`, `and_then`, `or_else`, `unwrap`, `expect` provide powerful ways to work with these types.
- **Type Safety**: Leverages Python's type hinting for compile-time checks.
- **Sealed Types**: `Result` and `Option` are sealed, preventing arbitrary subclassing and enforcing usage of provided variants (`Ok`/`Err`, `Some`/`None`).

## Installation

[TBD]

## Usage

### Result

```python
from accord.result import Result, Ok, Err

def divide(a: int, b: int) -> Result[int, str]:
    if b == 0:
        return Err("Division by zero is not allowed.")
    return Ok(a // b)

# Successful operation
result1 = divide(10, 2)
if result1.is_ok():
    print(f"Success: {result1.unwrap()}") # Output: Success: 5
else:
    print(f"Error: {result1.unwrap_err()}")

# Failed operation
result2 = divide(10, 0)
if result2.is_ok():
    print(f"Success: {result2.unwrap()}")
else:
    print(f"Error: {result2.unwrap_err()}") # Output: Error: Division by zero is not allowed.

# Using expect for cleaner unwrap with panic message
try:
    value = divide(10, 0).expect("Failed to divide numbers.")
except RuntimeError as e:
    print(e) # Output: Failed to divide numbers.: 'Division by zero is not allowed.'

# Chaining operations with and_then
def add_one_if_even(n: int) -> Result[int, str]:
    if n % 2 == 0:
        return Ok(n + 1)
    else:
        return Err("Number is odd")

divide(10, 2).and_then(add_one_if_even) # Ok(5) -> add_one_if_even(5) -> Err("Number is odd")
# Returns: Err('Number is odd')

divide(8, 2).and_then(add_one_if_even) # Ok(4) -> add_one_if_even(4) -> Ok(5)
# Returns: Ok(5)
```

### Option

```python
from accord.option import Option, Some, None

def find_user(user_id: int) -> Option[str]:
    if user_id == 1:
        return Some("Alice")
    return None

user1 = find_user(1)
if user1.is_some():
    print(f"User found: {user1.unwrap()}") # Output: User found: Alice
else:
    print("User not found.")

user2 = find_user(2)
print(f"User: {user2.unwrap_or('Guest')}") # Output: User: Guest

# Using expect
try:
    user_name = find_user(2).expect("User with ID 2 not found.")
except RuntimeError as e:
    print(e) # Output: User with ID 2 not found.

# Mapping over an Option
user_id_option: Option[int] = Some(123)
mapped_option = user_id_option.map(lambda x: f"User ID: {x}") # Some("User ID: 123")
None.map(lambda x: f"User ID: {x}") # None

# Chaining operations with and_then
def get_user_id_as_str(user_id: int) -> Option[str]:
    return Some(str(user_id))

Some(456).and_then(get_user_id_as_str) # Some(456) -> get_user_id_as_str(456) -> Some("456")
# Returns: Some("456")

None.and_then(get_user_id_as_str) # None -> Returns None
# Returns: None
```

## FAQ

### Is this README generated by AI?

Yes.

I was feeling lazy. I'll make it myself in the future.

### What is `Result` and `Option`?

- **`Result<T, E>`**: This type represents the outcome of an operation that might succeed or fail.

  - `Ok(T)`: Indicates success and holds a value of type `T`.
  - `Err(E)`: Indicates failure and holds an error value of type `E`.
  - It's Rust's primary way of handling recoverable errors without exceptions.

- **`Option<T>`**: This type represents a value that might be present or absent.
  - `Some(T)`: Indicates that a value of type `T` is present.
  - `None`: Indicates the absence of a value.
  - It's used for values that are optional, like the result of a search that might not find anything.

### Why use `Result` and `Option` in Python?

Python traditionally uses exceptions for errors and `None` for optional values. However, `Result` and `Option` offer several benefits:

- **Explicit Error Handling**: `Result` forces you to consider the error case at compile time (with type checkers) or runtime, making error handling more deliberate than relying on buried exceptions.
- **Clearer Intent**: `Option` makes it explicit when a value might be missing, distinguishing it from other forms of "empty" or "null" that might arise from different contexts.
- **Functional Programming Patterns**: These types are monadic, enabling elegant chaining of operations using methods like `map` and `and_then`, leading to more expressive and composable code.
- **Avoids `None` Ambiguity**: In Python, `None` can sometimes be ambiguous. `Option` clearly separates the absence of a value from other potential "falsy" values.

### How do `Ok`/`Err` and `Some`/`None` relate to `Result`/`Option`?

`Result` and `Option` are abstract base classes (or type aliases) that define the common interface. `Ok` and `Err` are concrete implementations of `Result`, and `Some` is a concrete implementation of `Option` (with `None` being a singleton for the absence of a value). You will always create an instance of `Ok`, `Err`, or `Some` (or use the `None` singleton), not directly of `Result` or `Option` themselves.

### What does "Sealed" mean?

When `Result` (and `Option`) are "sealed," it means that only the predefined variants (`Ok`, `Err` for `Result`; `Some`, `None` for `Option`) are allowed to inherit from them. This prevents users of the library from creating their own custom `Result` or `Option` types, ensuring that all `Result` or `Option` values are one of the intended, known types.

### What is the difference between `unwrap()` and `expect()`?

- **`unwrap()`**: Attempts to get the contained value. If the operation fails (`Err` or `None`), it raises a generic `RuntimeError` (or the original error for `Result.unwrap()`).
- **`expect(message)`**: Attempts to get the contained value. If the operation fails (`Err` or `None`), it raises a `RuntimeError` that includes your custom `message` along with the original error, providing more context about _why_ the operation was expected to succeed.

## Contributing

Contributions will be welcomed once we reach the release. Please feel free to open issues or submit pull requests freely then.

## License

[TBD]
