Source code for monawhat.either

"""Module implementing the Either monad for handling success/failure cases.

Examples:
    Basic usage for error handling:
    
    >>> from monawhat.either import Either
    >>> 
    >>> def safe_divide(a, b):
    ...     if b == 0:
    ...         return Either.left(f"Cannot divide {a} by zero")
    ...     return Either.right(a / b)
    >>> 
    >>> # Successful case
    >>> result = safe_divide(10, 2)
    >>> if result.is_right():
    ...     print(f"Result: {result.get()}")  # Output: Result: 5.0
    >>> 
    >>> # Error case
    >>> result = safe_divide(10, 0)
    >>> if result.is_left():
    ...     print(f"Error: {result.get()}")  # Output: Error: Cannot divide 10 by zero
    
    Chaining operations with bind:
    
    >>> def get_user(user_id):
    ...     # Simulate fetching a user from database
    ...     if user_id > 0:
    ...         return Either.right({"id": user_id, "email": "user@example.com"})
    ...     return Either.left(f"User with id {user_id} not found")
    >>> 
    >>> def send_email(user, message):
    ...     # Simulate sending an email
    ...     if "email" in user:
    ...         return Either.right(f"Email sent to {user['email']}")
    ...     return Either.left("Email address not found")
    >>> 
    >>> # Chain operations together
    >>> result = (Either.right(1)
    ...           .bind(get_user)
    ...           .bind(lambda user: send_email(user, "Welcome!")))
    >>> print(result)  # Output: Right(Email sent to user@example.com)
    
    Using pattern matching:
    
    >>> def process_result(result):
    ...     return result.match(
    ...         left_fn=lambda error: f"Operation failed: {error}",
    ...         right_fn=lambda value: f"Operation succeeded with value: {value}"
    ...     )
    >>> 
    >>> success = Either.right(42)
    >>> failure = Either.left("Invalid input")
    >>> 
    >>> print(process_result(success))  # Output: Operation succeeded with value: 42
    >>> print(process_result(failure))  # Output: Operation failed: Invalid input
"""

from typing import TypeVar, Generic, cast
from collections.abc import Callable

from monawhat.base import BaseMonad

A = TypeVar("A")  # Left type (typically error)
B = TypeVar("B")  # Right type (typically success)
C = TypeVar("C")  # Output type for transformations


[docs] class Either(Generic[A, B], BaseMonad[B]): """Base class for the Either monad. This class should not be instantiated directly. Either represents a value of one of two possible types (a disjoint union). An instance of Either is either a Left or a Right. """
[docs] def is_left(self) -> bool: """Check if this Either is a Left.""" raise NotImplementedError("Subclasses must implement this")
[docs] def is_right(self) -> bool: """Check if this Either is a Right.""" raise NotImplementedError("Subclasses must implement this")
[docs] def get_or_else(self, default: B) -> B: """Return the value if this is a Right, or the default value if it's a Left.""" raise NotImplementedError("Subclasses must implement this")
[docs] def get(self) -> A | B: """Return the contained value.""" raise NotImplementedError("Subclasses must implement this")
[docs] def match(self, left_fn: Callable[[A], C], right_fn: Callable[[B], C]) -> C: """Apply the appropriate function based on whether this is a Left or Right. Args: left_fn: Function to apply if this is a Left right_fn: Function to apply if this is a Right Returns: The result of applying the appropriate function """ if self.is_left(): return left_fn(cast(A, self.get())) else: return right_fn(cast(B, self.get()))
@classmethod def _pure_implementation(cls, value: B) -> "Right[A, B]": """Implementation of the pure operation for the Either monad.""" return Right(value)
[docs] @staticmethod def left(value: A) -> "Left[A, B]": """Create a Left instance containing the given value.""" return Left(value)
[docs] @staticmethod def right(value: B) -> "Right[A, B]": """Create a Right instance containing the given value.""" return Either._pure_implementation(value)
[docs] class Left(Either[A, B]): """The Left case of the Either monad, typically representing failure.""" def __init__(self, value: A) -> None: """Initialize a Left instance with a value. Args: value: The value to store in the Left instance. """ self._value: A = value
[docs] def is_left(self) -> bool: """Check if this Either is a Left.""" return True
[docs] def is_right(self) -> bool: """Check if this Either is a Right.""" return False
def _map_implementation(self, f: Callable[[B], C]) -> "Either[A, C]": """Left values ignore mapping operations.""" return Left(self._value) def _bind_implementation(self, f: Callable[[B], "Either[A, C]"]) -> "Either[A, C]": """Left values ignore bind operations.""" return Left(self._value)
[docs] def get_or_else(self, default: B) -> B: """Return the default value.""" return default
[docs] def get(self) -> A: """Return the contained value.""" return self._value
[docs] def __repr__(self) -> str: """Return a string representation of the Left instance.""" return f"Left({self._value})"