advanced-python-homework/controls/either.py

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