mirror of
https://github.com/correl/typesafe-monads.git
synced 2024-11-14 11:09:36 +00:00
Monoids
This commit is contained in:
parent
d38f617ba4
commit
f5b0e6e5e4
4 changed files with 125 additions and 2 deletions
|
@ -1,6 +1,7 @@
|
|||
from __future__ import annotations
|
||||
from typing import Any, Callable, Generic, Optional, TypeVar
|
||||
from typing import Any, Callable, Generic, List, Optional, TypeVar
|
||||
from .monad import Monad
|
||||
from .monoid import Monoid
|
||||
|
||||
T = TypeVar("T")
|
||||
S = TypeVar("S")
|
||||
|
@ -67,3 +68,45 @@ def maybe(value: T, predicate: Optional[Callable[[T], bool]] = None) -> Maybe[T]
|
|||
return Just(value)
|
||||
else:
|
||||
return Nothing()
|
||||
|
||||
|
||||
class First(Monoid[Maybe[T]]):
|
||||
@classmethod
|
||||
def mzero(cls) -> First:
|
||||
return First(Nothing())
|
||||
|
||||
def mappend(self, other: First):
|
||||
if isinstance(self.value, Just):
|
||||
return self
|
||||
else:
|
||||
return other
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<First {self.value}>"
|
||||
|
||||
__add__ = mappend
|
||||
|
||||
|
||||
def first(xs: List[Maybe[T]]) -> Maybe[T]:
|
||||
return First.mconcat(map(lambda x: First(x), xs)).value
|
||||
|
||||
|
||||
class Last(Monoid[Maybe[T]]):
|
||||
@classmethod
|
||||
def mzero(cls) -> Last:
|
||||
return Last(Nothing())
|
||||
|
||||
def mappend(self, other: Last):
|
||||
if isinstance(other.value, Just):
|
||||
return other
|
||||
else:
|
||||
return self
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<Last {self.value}>"
|
||||
|
||||
__add__ = mappend
|
||||
|
||||
|
||||
def last(xs: List[Maybe[T]]) -> Maybe[T]:
|
||||
return Last.mconcat(map(lambda x: Last(x), xs)).value
|
||||
|
|
34
monads/monoid.py
Normal file
34
monads/monoid.py
Normal file
|
@ -0,0 +1,34 @@
|
|||
from __future__ import annotations
|
||||
from functools import reduce
|
||||
from numbers import Number
|
||||
from typing import Any, Callable, Generic, Iterator, List, Type, TypeVar, Union
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class Monoid(Generic[T]):
|
||||
def __init__(self, value: T) -> None:
|
||||
self.value = value
|
||||
|
||||
# FIXME: Other type set to Any, as the proper value (Monoid[T]) is
|
||||
# reported as incompatible with subclass implementations due to a
|
||||
# flaw in mypy: https://github.com/python/mypy/issues/1317
|
||||
def mappend(self, other: Any) -> Monoid[T]:
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def mzero(cls) -> Monoid[T]:
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def mconcat(cls, xs: Iterator[Monoid[T]]) -> Monoid[T]:
|
||||
return reduce(cls.mappend, xs, cls.mzero())
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
return (
|
||||
isinstance(other, Monoid)
|
||||
and type(self) == type(other)
|
||||
and self.value == other.value
|
||||
)
|
||||
|
||||
__add__ = mappend
|
|
@ -1,4 +1,6 @@
|
|||
from monads.maybe import Maybe, Just, Nothing, maybe
|
||||
import pytest # type: ignore
|
||||
from typing import List
|
||||
from monads.maybe import Maybe, Just, Nothing, maybe, first, last
|
||||
|
||||
|
||||
def test_maybe_none():
|
||||
|
@ -15,3 +17,12 @@ def test_maybe_boolean_false():
|
|||
|
||||
def test_maybe_boolean_true():
|
||||
assert isinstance(maybe(True, predicate=bool), Just)
|
||||
|
||||
|
||||
def test_first() -> None:
|
||||
maybes: List[Maybe[int]] = [Nothing(), Just(1), Just(2)]
|
||||
assert Just(1) == first(maybes)
|
||||
|
||||
def test_last() -> None:
|
||||
maybes: List[Maybe[int]] = [Just(1), Just(2), Nothing()]
|
||||
assert Just(2) == last(maybes)
|
||||
|
|
35
tests/test_monoids.py
Normal file
35
tests/test_monoids.py
Normal file
|
@ -0,0 +1,35 @@
|
|||
import pytest # type: ignore
|
||||
from typing import Any, Callable, Type
|
||||
from monads.monoid import Monoid
|
||||
from monads.maybe import First, Last, Just
|
||||
|
||||
Constructor = Callable[[Any], Monoid]
|
||||
|
||||
|
||||
@pytest.fixture(
|
||||
scope="module", params=[lambda x: First(Just(x)), lambda x: Last(Just(x))]
|
||||
)
|
||||
def monoid(request) -> Constructor:
|
||||
return request.param
|
||||
|
||||
|
||||
def test_associative(monoid: Constructor) -> None:
|
||||
a: Monoid = monoid(1)
|
||||
b: Monoid = monoid(2)
|
||||
c: Monoid = monoid(3)
|
||||
|
||||
assert (a + b) + c == a + (b + c)
|
||||
|
||||
|
||||
def test_mconcat_empty(monoid: Constructor) -> None:
|
||||
cls: Type = type(monoid(1))
|
||||
zero: Monoid = cls.mzero()
|
||||
assert zero == cls.mconcat([])
|
||||
|
||||
def test_mconcat(monoid: Constructor) -> None:
|
||||
cls: Type = type(monoid(1))
|
||||
a: Monoid = monoid(1)
|
||||
b: Monoid = monoid(2)
|
||||
c: Monoid = monoid(3)
|
||||
expected: Monoid = a.mappend(b).mappend(c)
|
||||
assert expected == cls.mconcat([a, b, c])
|
Loading…
Reference in a new issue