tokencrawler/.venv/lib/python3.9/site-packages/oslash/maybe.py
2022-03-17 22:16:30 +01:00

259 lines
6.5 KiB
Python

from abc import abstractmethod
from functools import reduce, partial
from typing import Callable, Any, Generic, TypeVar, cast
from .typing import Applicative
from .typing import Functor
from .typing import Monoid
from .typing import Monad
TSource = TypeVar("TSource")
TResult = TypeVar("TResult")
class Maybe(Generic[TSource]):
"""Encapsulates an optional value.
The Maybe type encapsulates an optional value. A value of type
Maybe a either contains a value of (represented as Just a), or it is
empty (represented as Nothing). Using Maybe is a good way to deal
with errors or exceptional cases without resorting to drastic
measures such as error.
"""
@classmethod
def empty(cls) -> "Maybe[TSource]":
return Nothing()
@abstractmethod
def __add__(self, other: "Maybe[TSource]") -> "Maybe[TSource]":
raise NotImplementedError
@abstractmethod
def map(self, mapper: Callable[[TSource], TResult]) -> "Maybe[TResult]":
raise NotImplementedError
@classmethod
def pure(cls, value: Callable[[TSource], TResult]) -> "Maybe[Callable[[TSource], TResult]]":
raise NotImplementedError
@abstractmethod
def apply(self: "Maybe[Callable[[TSource], TResult]]", something: "Maybe[TSource]") -> "Maybe[TResult]":
raise NotImplementedError
@classmethod
@abstractmethod
def unit(cls, a: TSource) -> "Maybe[TSource]":
raise NotImplementedError
@abstractmethod
def bind(self, fn: Callable[[TSource], "Maybe[TResult]"]) -> "Maybe[TResult]":
raise NotImplementedError
@classmethod
def concat(cls, xs):
"""mconcat :: [m] -> m
Fold a list using the monoid. For most types, the default
definition for mconcat will be used, but the function is
included in the class definition so that an optimized version
can be provided for specific types.
"""
def reducer(a, b):
return a + b
return reduce(reducer, xs, cls.empty())
def __rmod__(self, fn):
"""Infix version of map.
Haskell: <$>
Example:
>>> (lambda x: x+2) % Just(40)
42
Returns a new Functor.
"""
return self.map(fn)
class Just(Maybe[TSource]):
"""A Maybe that contains a value.
Represents a Maybe that contains a value (represented as Just a).
"""
def __init__(self, value: TSource) -> None:
self._value = value
# Monoid Section
# ==============
def __add__(self, other: Maybe[TSource]) -> Maybe[TSource]:
# m `append` Nothing = m
if isinstance(other, Nothing):
return self
# Just m1 `append` Just m2 = Just (m1 `append` m2)
return other.map(
lambda other_value: cast(Any, self._value) + other_value if hasattr(self._value, "__add__") else Nothing()
)
# Functor Section
# ===============
def map(self, mapper: Callable[[TSource], TResult]) -> Maybe[TResult]:
# fmap f (Just x) = Just (f x)
result = mapper(self._value)
return Just(result)
# Applicative Section
# ===================
@classmethod
def pure(cls, value: Callable[[TSource], TResult]) -> "Just[Callable[[TSource], TResult]]":
return Just(value)
def apply(self: "Just[Callable[[TSource], TResult]]", something: Maybe[TSource]) -> Maybe[TResult]:
def mapper(other_value):
try:
return self._value(other_value)
except TypeError:
return partial(self._value, other_value)
return something.map(mapper)
# Monad Section
# =============
@classmethod
def unit(cls, value: TSource) -> Maybe[TSource]:
return Just(value)
def bind(self, func: Callable[[TSource], Maybe[TResult]]) -> Maybe[TResult]:
"""Just x >>= f = f x."""
value = self._value
return func(value)
# Utilities Section
# =================
def is_just(self) -> bool:
return True
def is_nothing(self) -> bool:
return False
# Operator Overloads Section
# ==========================
def __bool__(self) -> bool:
"""Convert Just to bool."""
return bool(self._value)
def __eq__(self, other) -> bool:
"""Return self == other."""
if isinstance(other, Nothing):
return False
return bool(other.map(lambda other_value: other_value == self._value))
def __str__(self) -> str:
return "Just %s" % self._value
def __repr__(self) -> str:
return str(self)
class Nothing(Maybe[TSource]):
"""Represents an empty Maybe.
Represents an empty Maybe that holds nothing (in which case it has
the value of Nothing).
"""
# Monoid Section
# ==============
def __add__(self, other: Maybe) -> Maybe:
# m `append` Nothing = m
return other
# Functor Section
# ===============
def map(self, mapper: Callable[[TSource], TResult]) -> Maybe[TResult]:
return Nothing()
# Applicative Section
# ===================
@classmethod
def pure(cls, value: Callable[[TSource], TResult]) -> Maybe[Callable[[TSource], TResult]]:
return Nothing()
def apply(self: "Nothing[Callable[[TSource], TResult]]", something: Maybe[TSource]) -> Maybe[TResult]:
return Nothing()
# Monad Section
# =============
@classmethod
def unit(cls, value: TSource) -> Maybe[TSource]:
return cls()
def bind(self, func: Callable[[TSource], Maybe[TResult]]) -> Maybe[TResult]:
"""Nothing >>= f = Nothing
Nothing in, Nothing out.
"""
return Nothing()
# Utilities Section
# =================
def is_pure(self) -> bool:
return False
def is_nothing(self) -> bool:
return True
# Operator Overloads Section
# ==========================
def __eq__(self, other) -> bool:
return isinstance(other, Nothing)
def __str__(self) -> str:
return "Nothing"
def __repr__(self) -> str:
return str(self)
assert issubclass(Just, Maybe)
assert issubclass(Nothing, Maybe)
assert isinstance(Maybe, Monoid)
assert isinstance(Maybe, Functor)
assert isinstance(Maybe, Applicative)
assert isinstance(Maybe, Monad)
assert isinstance(Just, Monoid)
assert isinstance(Just, Functor)
assert isinstance(Just, Applicative)
assert isinstance(Just, Monad)
assert isinstance(Nothing, Monoid)
assert isinstance(Nothing, Functor)
assert isinstance(Nothing, Applicative)
assert isinstance(Nothing, Monad)