259 lines
6.5 KiB
Python
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)
|