111 lines
3.1 KiB
Python
111 lines
3.1 KiB
Python
|
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
|