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

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))