54 lines
1.3 KiB
Python
54 lines
1.3 KiB
Python
"""A Maybe monad. Requires Python >= 3.7 for type hints."""
|
|
from __future__ import annotations
|
|
|
|
from typing import Callable
|
|
from typing import Generic
|
|
from typing import Optional
|
|
from typing import TypeVar
|
|
from typing import Union
|
|
|
|
|
|
T = TypeVar("T")
|
|
U = TypeVar("U")
|
|
|
|
|
|
class Maybe(Generic[T]):
|
|
def __init__(self, value: Union[Optional[T], Maybe[T]] = None):
|
|
if isinstance(value, Maybe):
|
|
self.value: Optional[T] = value.value
|
|
else:
|
|
self.value = value
|
|
|
|
def __rshift__(self, func: Callable[[Optional[T]], Maybe[U]]) -> Maybe[U]:
|
|
return self.bind(func)
|
|
|
|
def bind(self, func: Callable[[Optional[T]], Maybe[U]]) -> Maybe[U]:
|
|
return func(self.value)
|
|
|
|
def __str__(self):
|
|
return f"{self.__class__.__name__}({self.value!r})"
|
|
|
|
|
|
def plus_one(value: Optional[int]) -> Maybe[int]:
|
|
if value is not None:
|
|
return Maybe(value + 1)
|
|
return Maybe(None)
|
|
|
|
|
|
def currency(value: Optional[int]) -> Maybe[str]:
|
|
if value is not None:
|
|
return Maybe(f"${value}.00")
|
|
return Maybe(None)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
test_cases = [1, 99, None, 4]
|
|
|
|
for case in test_cases:
|
|
result = Maybe(case) >> plus_one >> currency
|
|
|
|
# or..
|
|
# result = Maybe(case).bind(plus_one).bind(currency)
|
|
|
|
print(f"{str(case):<4} -> {result}")
|