Source code for pymonad.monoid
# --------------------------------------------------------
# (c) Copyright 2014, 2020 by Jason DeLaat.
# Licensed under BSD 3-clause licence.
# --------------------------------------------------------
""" Implements the base Monoid type.
A monoid is an algebraic structure consisting of a set of objects, S,
such as integers; strings; etc., and an operation usually denoted as
'+' which obeys the following rules:
#. Closure: If 'a' and 'b' are in S, then 'a + b' is also in S.
#. Identity: There exists an element in S (denoted 0) such that
a + 0 = 0 + a = a
#. Associativity: (a + b) + c = a + (b + c)
For monoid types, the '+' operation is implemented by the method
'mplus' and the static method 'mzero' is defined to return the
identity element of the type.
For example, integers can be monoids in two ways:
#. mzero = 0 and mplus = addition
#. mzero = 1 and mplus = multiplication
String can also form a monoid where mzero is the empty string and
mplus is concatenation.
"""
from typing import Any, Generic, List, TypeVar # pylint: disable=unused-import
T = TypeVar("T") # pylint: disable=invalid-name
class _MonoidZeroMeta(type):
def __add__(cls, other):
return other
def __radd__(cls, other):
return other
def __repr__(cls):
return "MZERO"
[docs]class ZERO(metaclass=_MonoidZeroMeta): # pylint: disable=too-few-public-methods
""" A generic zero/identity element for monoids.
The ZERO class acts as a constant/singleton with monoid addition
implemented on the class itself to always return the other
element. It is not actually possible to create an instance of ZERO
as calling the constructor simply returns the class itself.
Example:
>>> ZERO == ZERO() # True.
>>> ZERO + 10 # 10
>>> 'hello' + ZERO # 'hello'
"""
def __new__(cls):
return ZERO
[docs]class Monoid(Generic[T]):
""" Abstract base class for Monoid instances.
To implement a monoid instance, users should create a sub-class of
Monoid and implement the mzero and mplus methods. Additionally, it
is the implementers responsibility to ensure that the
implementation adheres to the closure, identity and associativity
laws for monoids.
"""
def __init__(self, value: T) -> None:
""" Initializes the monoid element to 'value'. """
self.value = value
def __add__(self: "Monoid[T]", other: "Monoid[T]") -> "Monoid[T]":
""" The 'mplus' operator. """
return self.mplus(other)
def __eq__(self: "Monoid[T]", other: "Monoid[T]") -> bool:
return self.value == other.value
[docs] @staticmethod
def mzero() -> "Monoid[Any]":
"""
A static method which simply returns the identity value for the monoid type.
This method must be overridden in subclasses to create custom monoids.
See also: the mzero function.
"""
raise NotImplementedError
[docs] def mplus(self, other):
"""
The defining operation of the monoid. This method must be overridden in subclasses
and should meet the following conditions:
#. x + 0 == 0 + x == x
#. (x + y) + z == x + (y + z) == x + y + z
Where 'x', 'y', and 'z' are monoid values, '0' is the mzero (the identity value) and '+'
is mplus.
"""
raise NotImplementedError
[docs]def mzero(monoid_type):
"""
Returns the identity value for monoid_type. Raises TypeError if
monoid_type is not a valid monoid.
There are a number of builtin types that can operate as monoids
and they can be used as such as is. These "natural" monoids are:
int, float, str, and list. While thee mzero method will work on
monoids derived from the Monoid class, this mzero function will
work for *all* monoid types, including the "natural" monoids. For
this reason it is preferable to call this function rather than
calling the mzero method directly unless you know for sure what
type of monoid you're dealing with.
"""
try:
return monoid_type.mzero()
except AttributeError:
if (
isinstance(monoid_type, (int, float))
or monoid_type == int
or monoid_type == float
): # pylint: disable=no-else-return
return 0
elif isinstance(monoid_type, str) or monoid_type == str:
return ""
elif isinstance(monoid_type, list) or monoid_type == list:
return []
else:
raise TypeError(str(monoid_type) + " is not a Monoid.")
[docs]def mconcat(monoid_list: List[Monoid[T]]) -> Monoid[T]:
"""
Takes a list of monoid values and reduces them to a single value by applying the
mplus operation to each all elements of the list.
"""
result = mzero(monoid_list[0])
for value in monoid_list:
result += value
return result