RosettaCodeData/Task/Monads-Maybe-monad/Python/monads-maybe-monad.py

96 lines
2.4 KiB
Python
Raw Permalink Normal View History

2023-07-01 11:58:00 -04:00
from __future__ import annotations
2026-02-01 16:33:20 -08:00
from abc import ABC
from abc import abstractmethod
from typing import Any
2023-07-01 11:58:00 -04:00
from typing import Callable
2026-02-01 16:33:20 -08:00
class Maybe[T](ABC):
@abstractmethod
def is_some(self) -> bool:
"""Return `True` if the option is a `Some`."""
2023-07-01 11:58:00 -04:00
2026-02-01 16:33:20 -08:00
@abstractmethod
def is_none(self) -> bool:
"""Return `True` if the option is a `Nothing`."""
2023-07-01 11:58:00 -04:00
2026-02-01 16:33:20 -08:00
@abstractmethod
def bind[U](self, func: Callable[[T], Maybe[U]]) -> Maybe[U]:
"""Apply `func` to this value if it's a `Some`."""
2023-07-01 11:58:00 -04:00
2026-02-01 16:33:20 -08:00
def and_then[U](self, func: Callable[[T], Maybe[U]]) -> Maybe[U]:
"Alias for `bind`."
2023-07-01 11:58:00 -04:00
return self.bind(func)
2026-02-01 16:33:20 -08:00
def __rshift__[U](self, func: Callable[[T], Maybe[U]]) -> Maybe[U]:
"Alias for `bind`."
return self.bind(func)
@abstractmethod
def or_else(self, func: Callable[[], Maybe[T]]) -> Maybe[T]:
"""Return self if it's a `Some`, otherwise the result of `func`."""
class Some[T](Maybe[T]):
def __init__(self, value: T) -> None:
self.value = value
def __str__(self) -> str:
return f"Some({self.value!r})"
def is_some(self) -> bool:
return True
def is_none(self) -> bool:
return False
def bind[U](self, func: Callable[[T], Maybe[U]]) -> Maybe[U]:
2023-07-01 11:58:00 -04:00
return func(self.value)
2026-02-01 16:33:20 -08:00
def or_else(self, func: Callable[[], Maybe[T]]) -> Maybe[T]:
return self
2023-07-01 11:58:00 -04:00
2026-02-01 16:33:20 -08:00
class Nothing[T](Maybe[T]):
def __str__(self) -> str:
return "Nothing()"
2023-07-01 11:58:00 -04:00
2026-02-01 16:33:20 -08:00
def is_some(self) -> bool:
return False
2023-07-01 11:58:00 -04:00
2026-02-01 16:33:20 -08:00
def is_none(self) -> bool:
return True
def bind[U](self, func: Callable[[T], Maybe[U]]) -> Maybe[U]:
return NOTHING
def or_else(self, func: Callable[[], Maybe[T]]) -> Maybe[T]:
return func()
NOTHING = Nothing[Any]()
2023-07-01 11:58:00 -04:00
if __name__ == "__main__":
2026-02-01 16:33:20 -08:00
def plus_one(value: int) -> Maybe[int]:
return Some(value + 1)
def currency(value: int) -> Maybe[str]:
return Some(f"${value}.00")
values: list[Maybe[int]] = [Some(1), Some(99), NOTHING, Some(4)]
# Using `>>` as a bind operator
for value in values:
result = value >> plus_one >> currency
print(f"{value} -> {result}")
2024-03-06 22:25:12 -08:00
2026-02-01 16:33:20 -08:00
print("---")
2024-03-06 22:25:12 -08:00
2026-02-01 16:33:20 -08:00
# The same, but using lambda functions with the bind method.
for value in values:
result = value.bind(lambda v: Some(v + 1)).bind(lambda v: Some(f"${v}.00"))
print(f"{value} -> {result}")