"""Module implementing the Option monad for handling optional values.
Examples:
Create a Just value:
>>> maybe_value = Maybe.just(42)
>>> maybe_value.get()
42
Create a Nothing value:
>>> empty = Maybe.nothing()
>>> empty.is_nothing()
True
Map over a Maybe:
>>> maybe_value = Maybe.just(42)
>>> doubled = maybe_value.map(lambda x: x * 2)
>>> doubled.get()
84
Handle None values:
>>> value = Maybe.from_optional(None)
>>> value.get_or_else(0)
0
Chain computations:
>>> def divide(x: int, y: int) -> Maybe[float]:
... return Maybe.just(x / y) if y != 0 else Maybe.nothing()
>>> result = Maybe.just(10).bind(lambda x: divide(x, 2))
>>> result.get()
5.0
"""
from typing import TypeVar
from collections.abc import Callable
from monawhat.base import BaseMonad
from monawhat.either import Either
A = TypeVar("A") # Input type
B = TypeVar("B") # Output type
E = TypeVar("E") # Error type
[docs]
class Maybe(BaseMonad[A]):
"""Base class for the Option monad. This class should not be instantiated directly.
Option represents a value that may or may not be present.
An instance of Option is either a Just (containing a value) or a Nothing.
"""
[docs]
def is_just(self) -> bool:
"""Check if this Option is a Just."""
raise NotImplementedError("Subclasses must implement this")
[docs]
def is_nothing(self) -> bool:
"""Check if this Option is a Nothing."""
raise NotImplementedError("Subclasses must implement this")
[docs]
def get_or_else(self, default: A) -> A:
"""Return the value if this is a Just, or the default value if it's a Nothing."""
raise NotImplementedError("Subclasses must implement this")
[docs]
def get(self) -> A:
"""Return the contained value if Just, or raise an error if Nothing."""
raise NotImplementedError("Subclasses must implement this")
[docs]
def to_either(self, error: E) -> "Either[E, A]":
"""Convert this Maybe to an Either.
Args:
error: The error value to use if this is Nothing
Returns:
Either.left(error) if this is Nothing, otherwise Either.right(self.get())
"""
if self.is_nothing():
return Either.left(error)
return Either.right(self.get())
@classmethod
def _pure_implementation(cls, value: A) -> "Just[A]":
"""Implementation of the pure operation for the Maybe monad."""
return Just(value)
[docs]
@staticmethod
def just(value: A) -> "Just[A]":
"""Create a Just instance containing the given value."""
return Maybe._pure_implementation(value)
[docs]
@staticmethod
def nothing() -> "Nothing[A]":
"""Create a Nothing instance."""
return Nothing()
[docs]
@staticmethod
def from_optional(value: A | None) -> "Maybe[A]":
"""Create a Maybe from an optional value.
Args:
value: Value that might be None
Returns:
Just(value) if value is not None, otherwise Nothing()
"""
return Maybe.just(value) if value is not None else Maybe.nothing()
[docs]
class Just(Maybe[A]):
"""The Just case of the Option monad, representing a present value."""
def __init__(self, value: A) -> None:
"""Initialize a Just instance with a value.
Args:
value: The value to store in the Just instance.
"""
self._value: A = value
[docs]
def is_just(self) -> bool:
"""Check if this Option is a Just."""
return True
[docs]
def is_nothing(self) -> bool:
"""Check if this Option is a Nothing."""
return False
def _map_implementation(self, f: Callable[[A], B]) -> "Maybe[B]":
"""Apply function f to the contained value and wrap the result in a new Just."""
return Just(f(self._value))
def _bind_implementation(self, f: Callable[[A], "Maybe[B]"]) -> "Maybe[B]":
"""Apply function f that returns an Option monad to the contained value and return that monad directly."""
return f(self._value)
[docs]
def get_or_else(self, default: A) -> A:
"""Return the contained value."""
return self._value
[docs]
def get(self) -> A:
"""Return the contained value."""
return self._value
[docs]
def __repr__(self) -> str:
"""Return a string representation of the Just instance."""
return f"Just({self._value})"
[docs]
class Nothing(Maybe[A]):
"""The Nothing case of the Option monad, representing the absence of a value."""
[docs]
def is_just(self) -> bool:
"""Check if this Option is a Just."""
return False
[docs]
def is_nothing(self) -> bool:
"""Check if this Option is a Nothing."""
return True
def _map_implementation(self, f: Callable[[A], B]) -> "Maybe[B]":
"""Nothing values ignore mapping operations."""
return Nothing()
def _bind_implementation(self, f: Callable[[A], "Maybe[B]"]) -> "Maybe[B]":
"""Nothing values ignore bind operations."""
return Nothing()
[docs]
def get_or_else(self, default: A) -> A:
"""Return the default value."""
return default
[docs]
def get(self) -> A:
"""Raise an error since Nothing contains no value."""
raise ValueError("Cannot get value from Nothing")
[docs]
def __repr__(self) -> str:
"""Return a string representation of the Nothing instance."""
return "Nothing()"