121 lines
3.3 KiB
Python
121 lines
3.3 KiB
Python
from typing import Callable, Tuple, Any, TypeVar, Generic, Union, cast
|
|
|
|
from .typing import Functor
|
|
from .typing import Monad
|
|
from .typing import Monoid
|
|
|
|
TLog = TypeVar("TLog", str, Monoid)
|
|
TSource = TypeVar("TSource")
|
|
TResult = TypeVar("TResult")
|
|
|
|
|
|
class Writer(Generic[TSource, TLog]):
|
|
"""The writer monad."""
|
|
|
|
def __init__(self, value: TSource, log: TLog) -> None:
|
|
"""Initialize a new writer.
|
|
|
|
value Value to
|
|
"""
|
|
|
|
self._value: Tuple[TSource, TLog] = (value, log)
|
|
|
|
def map(self, func: Callable[[Tuple[TSource, TLog]], Tuple[TResult, TLog]]) -> 'Writer[TResult, TLog]':
|
|
"""Map a function func over the Writer value.
|
|
|
|
Haskell:
|
|
fmap f m = Writer $ let (a, w) = runWriter m in (f a, w)
|
|
|
|
Keyword arguments:
|
|
func -- Mapper function:
|
|
"""
|
|
a, w = self.run()
|
|
b, _w = func((a, w))
|
|
return Writer(b, _w)
|
|
|
|
def bind(self, func: Callable[[TSource], 'Writer[TResult, TLog]']) -> 'Writer[TResult, TLog]':
|
|
"""Flat is better than nested.
|
|
|
|
Haskell:
|
|
(Writer (x, v)) >>= f = let
|
|
(Writer (y, v')) = f x in Writer (y, v `append` v')
|
|
"""
|
|
a, w = self.run()
|
|
b, w_ = func(a).run()
|
|
|
|
w__ = w + w_
|
|
|
|
return Writer(b, w__)
|
|
|
|
@classmethod
|
|
def unit(cls, value: TSource) -> 'Writer[TSource, TLog]':
|
|
"""Wrap a single value in a Writer.
|
|
|
|
Use the factory method to create *Writer classes that
|
|
uses a different monoid than str, or use the constructor
|
|
directly.
|
|
"""
|
|
return Writer(value, log=cast(TLog, ""))
|
|
|
|
def run(self) -> Tuple[TSource, TLog]:
|
|
"""Extract value from Writer.
|
|
|
|
This is the inverse function of the constructor and converts the
|
|
Writer to s simple tuple.
|
|
"""
|
|
return self._value
|
|
|
|
@staticmethod
|
|
def apply_log(a: tuple, func: Callable[[Any], Tuple[TSource, TLog]]) -> Tuple[TSource, TLog]:
|
|
"""Apply a function to a value with a log.
|
|
|
|
Helper function to apply a function to a value with a log tuple.
|
|
"""
|
|
value, log = a
|
|
new, entry = func(value)
|
|
return new, log + entry
|
|
|
|
@classmethod
|
|
def create(cls, class_name: str, monoid_type=Union[Monoid, str]):
|
|
"""Create Writer subclass using specified monoid type. lets us
|
|
create a Writer that uses a different monoid than str for the
|
|
log.
|
|
|
|
Usage:
|
|
StringWriter = Writer.create("StringWriter", str)
|
|
IntWriter = Writer.create("IntWriter", int)
|
|
...
|
|
"""
|
|
|
|
def unit(cls, value):
|
|
if hasattr(monoid_type, "empty"):
|
|
log = monoid_type.empty()
|
|
else:
|
|
log = monoid_type()
|
|
|
|
return cls(value, log)
|
|
|
|
return type(class_name, (Writer, ), dict(unit=classmethod(unit)))
|
|
|
|
def __eq__(self, other) -> bool:
|
|
return self.run() == other.run()
|
|
|
|
def __str__(self) -> str:
|
|
return "%s :: %s" % self.run()
|
|
|
|
def __repr__(self) -> str:
|
|
return str(self)
|
|
|
|
|
|
class MonadWriter(Writer[Any, TLog]):
|
|
|
|
@classmethod
|
|
def tell(cls, log: TLog) -> 'MonadWriter':
|
|
return cls(None, log)
|
|
|
|
|
|
StringWriter = Writer.create("StringWriter", str)
|
|
|
|
|
|
assert(isinstance(Writer, Functor))
|
|
assert(isinstance(Writer, Monad))
|