from abc import ABC, abstractmethod from typing import Generic, TypeVar, Callable L = TypeVar('L') R = TypeVar('R') N = TypeVar('N') class Either(ABC, Generic[L, R]): 'A monad-like object to handle errors in a functional style.' @abstractmethod def is_left(self) -> bool: 'Returns `True` if the objects belongs to `Left` class' pass @abstractmethod def is_right(self) -> bool: 'Returns `True` if the objects belongs to `Right` class' pass @abstractmethod def map_left(self, f: Callable[[L], N]) -> 'Either[N, R]': '''Applies transform `f` to the value if type is `Left`, but does not touch the value if type is `Right`.''' pass @abstractmethod def map_right(self, f: Callable[[R], N]) -> 'Either[L, N]': '''Applies transform `f` to the right if type is `Right`, but does not touch the value if type is `Left`.''' pass @abstractmethod def get_left_or(self, default: L) -> L: '''Unwraps the underlying value if `Left`, otherwise returns the provided default value.''' pass @abstractmethod def get_left(self) -> L: '''Unwraps the underlying value if `Left`, panics if `Right`.''' pass @abstractmethod def get_right_or(self, default: R) -> R: '''Unwraps the underlying value if `Right`, otherwise returns the provided default value.''' pass @abstractmethod def get_right(self) -> R: '''Unwraps the underlying error if `Right`, panics if `Left`.''' pass class Left(Either[L, R]): def __init__(self, value: L) -> None: super().__init__() self._left_value = value def is_left(self) -> bool: return True def is_right(self) -> bool: return not self.is_left() def map_left(self, f: Callable[[L], N]) -> 'Either[N, R]': return Left(f(self._left_value)) def map_right(self, f: Callable[[R], N]) -> 'Either[L, N]': return Left(self._left_value) def get_left_or(self, default: L) -> L: return self._left_value def get_left(self) -> L: return self._left_value def get_right_or(self, default: R) -> R: return default def get_right(self) -> R: raise RuntimeError(f'Object {self.__repr__()} is nor of Error type') class Right(Either[L, R]): def __init__(self, value: R) -> None: super().__init__() self._right_value = value def is_left(self) -> bool: return False def is_right(self) -> bool: return not self.is_left() def map_left(self, f: Callable[[L], N]) -> 'Either[N, R]': return Right(self._right_value) def map_right(self, f: Callable[[R], N]) -> 'Either[L, N]': return Right(f(self._right_value)) def get_left_or(self, default: L) -> L: return default def get_left(self) -> L: raise RuntimeError(f'Attempt to unwrap object {self.__repr__()}, which is of Error type') def get_right_or(self, default: R) -> R: return self._right_value def get_right(self) -> R: return self._right_value