push
This commit is contained in:
commit
8eb63a0625
6 changed files with 1020 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
__pycache__/
|
||||||
|
**/__pycache__/
|
||||||
44
lib/__init__.py
Normal file
44
lib/__init__.py
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
from .typeclasses import (
|
||||||
|
TypeVar,
|
||||||
|
Generic,
|
||||||
|
Semigroup,
|
||||||
|
Monoid,
|
||||||
|
Functor,
|
||||||
|
Applicative,
|
||||||
|
Monad,
|
||||||
|
MonadTransformer,
|
||||||
|
ListMonoid,
|
||||||
|
StringMonoid,
|
||||||
|
SumMonoid,
|
||||||
|
ProductMonoid,
|
||||||
|
A, B, C, S, W, R, E, F,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .monads import (
|
||||||
|
Maybe, Just, Nothing,
|
||||||
|
Either, Right, Left,
|
||||||
|
List,
|
||||||
|
IO,
|
||||||
|
Writer,
|
||||||
|
State,
|
||||||
|
Reader,
|
||||||
|
MaybeT,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .classmethod import Classmethod, Classmthod
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"TypeVar", "Generic",
|
||||||
|
"Semigroup", "Monoid", "Functor", "Applicative", "Monad", "MonadTransformer",
|
||||||
|
"ListMonoid", "StringMonoid", "SumMonoid", "ProductMonoid",
|
||||||
|
"A", "B", "C", "S", "W", "R", "E", "F",
|
||||||
|
"Maybe", "Just", "Nothing",
|
||||||
|
"Either", "Right", "Left",
|
||||||
|
"List",
|
||||||
|
"IO",
|
||||||
|
"Writer",
|
||||||
|
"State",
|
||||||
|
"Reader",
|
||||||
|
"MaybeT",
|
||||||
|
"Classmethod", "Classmthod",
|
||||||
|
]
|
||||||
30
lib/classmethod.py
Normal file
30
lib/classmethod.py
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
||||||
|
class Classmethod:
|
||||||
|
def __init__(self, func):
|
||||||
|
self.func = func
|
||||||
|
self._attribute_name: str = getattr(func, "__name__", "<classmethod>")
|
||||||
|
|
||||||
|
def __set_name__(self, owner, name: str):
|
||||||
|
self._attribute_name = name
|
||||||
|
|
||||||
|
def __get__(self, instance, owner=None):
|
||||||
|
if owner is None:
|
||||||
|
owner = type(instance)
|
||||||
|
func = self.func
|
||||||
|
attr = self._attribute_name
|
||||||
|
|
||||||
|
def bound(*args, **kwargs):
|
||||||
|
return func(owner, *args, **kwargs)
|
||||||
|
|
||||||
|
bound.__name__ = attr
|
||||||
|
bound.__qualname__ = f"{owner.__qualname__}.{attr}"
|
||||||
|
return bound
|
||||||
|
|
||||||
|
def __set__(self, instance, value):
|
||||||
|
raise AttributeError("Classmethod reassigend")
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"Classmethod({self.func!r})"
|
||||||
|
Classmthod = Classmethod
|
||||||
649
lib/monads.py
Normal file
649
lib/monads.py
Normal file
|
|
@ -0,0 +1,649 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
from abc import abstractmethod
|
||||||
|
|
||||||
|
from .typeclasses import (
|
||||||
|
Applicative,
|
||||||
|
Generic,
|
||||||
|
Monad,
|
||||||
|
MonadTransformer,
|
||||||
|
Monoid,
|
||||||
|
ListMonoid,
|
||||||
|
)
|
||||||
|
from .classmethod import Classmethod
|
||||||
|
|
||||||
|
|
||||||
|
class Maybe(Monad):
|
||||||
|
@classmethod
|
||||||
|
def pure(cls, value) -> "Maybe":
|
||||||
|
return Just(value)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def of(cls, value) -> "Maybe":
|
||||||
|
return Nothing() if value is None else Just(value)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_either(cls, either: "Either") -> "Maybe":
|
||||||
|
return Just(either._value) if either.is_right() else Nothing()
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def bind(self, func) -> "Maybe": ...
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def is_just(self) -> bool: ...
|
||||||
|
|
||||||
|
def is_nothing(self) -> bool:
|
||||||
|
return not self.is_just()
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_or(self, default): ...
|
||||||
|
|
||||||
|
def get_or_raise(self, exc=None):
|
||||||
|
if self.is_just():
|
||||||
|
return self.get_or(None)
|
||||||
|
raise (exc if exc is not None else ValueError("Nothing.get_or_raise"))
|
||||||
|
|
||||||
|
def filter(self, predicate) -> "Maybe":
|
||||||
|
return self.bind(lambda x: Just(x) if predicate(x) else Nothing())
|
||||||
|
|
||||||
|
def or_else(self, alternative: "Maybe") -> "Maybe":
|
||||||
|
return self if self.is_just() else alternative
|
||||||
|
|
||||||
|
def to_either(self, error) -> "Either":
|
||||||
|
if self.is_just():
|
||||||
|
return Right(self.get_or(None))
|
||||||
|
return Left(error)
|
||||||
|
|
||||||
|
def to_list(self) -> "List":
|
||||||
|
return List([self.get_or(None)]) if self.is_just() else List([])
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
if self.is_just():
|
||||||
|
yield self.get_or(None)
|
||||||
|
|
||||||
|
def __bool__(self) -> bool:
|
||||||
|
return self.is_just()
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def __eq__(self, other: object) -> bool: ...
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def __hash__(self) -> int: ...
|
||||||
|
|
||||||
|
|
||||||
|
class Just(Maybe):
|
||||||
|
__slots__ = ("_value",)
|
||||||
|
|
||||||
|
def __init__(self, value):
|
||||||
|
self._value = value
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def pure(cls, value) -> "Just":
|
||||||
|
return cls(value)
|
||||||
|
|
||||||
|
def bind(self, func) -> Maybe:
|
||||||
|
return func(self._value)
|
||||||
|
|
||||||
|
def fmap(self, func) -> Maybe:
|
||||||
|
return Just(func(self._value))
|
||||||
|
|
||||||
|
def apply(self, wrapped_func: Applicative) -> Maybe:
|
||||||
|
if isinstance(wrapped_func, Just):
|
||||||
|
return Just(wrapped_func._value(self._value))
|
||||||
|
return Nothing()
|
||||||
|
|
||||||
|
def get_or(self, default):
|
||||||
|
return self._value
|
||||||
|
|
||||||
|
def is_just(self) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"Just({self._value!r})"
|
||||||
|
|
||||||
|
def __eq__(self, other: object) -> bool:
|
||||||
|
return isinstance(other, Just) and self._value == other._value
|
||||||
|
|
||||||
|
def __hash__(self) -> int:
|
||||||
|
return hash(("Just", self._value))
|
||||||
|
|
||||||
|
|
||||||
|
class Nothing(Maybe):
|
||||||
|
_instance = None
|
||||||
|
|
||||||
|
def __new__(cls):
|
||||||
|
if cls._instance is None:
|
||||||
|
cls._instance = super().__new__(cls)
|
||||||
|
return cls._instance
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def pure(cls, value) -> "Just":
|
||||||
|
return Just(value)
|
||||||
|
|
||||||
|
def bind(self, func) -> "Nothing":
|
||||||
|
return self
|
||||||
|
|
||||||
|
def fmap(self, func) -> "Nothing":
|
||||||
|
return self
|
||||||
|
|
||||||
|
def apply(self, wrapped_func: Applicative) -> "Nothing":
|
||||||
|
return self
|
||||||
|
|
||||||
|
def get_or(self, default):
|
||||||
|
return default
|
||||||
|
|
||||||
|
def is_just(self) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return "Nothing"
|
||||||
|
|
||||||
|
def __eq__(self, other: object) -> bool:
|
||||||
|
return isinstance(other, Nothing)
|
||||||
|
|
||||||
|
def __hash__(self) -> int:
|
||||||
|
return hash("Nothing")
|
||||||
|
|
||||||
|
|
||||||
|
class Either(Monad):
|
||||||
|
@classmethod
|
||||||
|
def pure(cls, value) -> "Right":
|
||||||
|
return Right(value)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def try_(cls, func, *args, **kwargs) -> "Either":
|
||||||
|
try:
|
||||||
|
return Right(func(*args, **kwargs))
|
||||||
|
except Exception as exc:
|
||||||
|
return Left(exc)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_maybe(cls, maybe: Maybe, error) -> "Either":
|
||||||
|
return Right(maybe.get_or(None)) if maybe.is_just() else Left(error)
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def bind(self, func) -> "Either": ...
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def is_right(self) -> bool: ...
|
||||||
|
|
||||||
|
def is_left(self) -> bool:
|
||||||
|
return not self.is_right()
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_or(self, default): ...
|
||||||
|
|
||||||
|
def get_or_raise(self):
|
||||||
|
if self.is_right():
|
||||||
|
return self.get_or(None)
|
||||||
|
err = self._error if hasattr(self, "_error") else ValueError("Left")
|
||||||
|
if isinstance(err, BaseException):
|
||||||
|
raise err
|
||||||
|
raise ValueError(err)
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def map_left(self, func) -> "Either": ...
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def swap(self) -> "Either": ...
|
||||||
|
|
||||||
|
def to_maybe(self) -> Maybe:
|
||||||
|
return Just(self.get_or(None)) if self.is_right() else Nothing()
|
||||||
|
|
||||||
|
def __bool__(self) -> bool:
|
||||||
|
return self.is_right()
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def __eq__(self, other: object) -> bool: ...
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def __hash__(self) -> int: ...
|
||||||
|
|
||||||
|
|
||||||
|
class Right(Either):
|
||||||
|
__slots__ = ("_value",)
|
||||||
|
|
||||||
|
def __init__(self, value):
|
||||||
|
self._value = value
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def pure(cls, value) -> "Right":
|
||||||
|
return cls(value)
|
||||||
|
|
||||||
|
def bind(self, func) -> Either:
|
||||||
|
try:
|
||||||
|
return func(self._value)
|
||||||
|
except Exception as exc:
|
||||||
|
return Left(exc)
|
||||||
|
|
||||||
|
def fmap(self, func) -> Either:
|
||||||
|
try:
|
||||||
|
return Right(func(self._value))
|
||||||
|
except Exception as exc:
|
||||||
|
return Left(exc)
|
||||||
|
|
||||||
|
def apply(self, wrapped_func: Applicative) -> Either:
|
||||||
|
if isinstance(wrapped_func, Right):
|
||||||
|
return Right(wrapped_func._value(self._value))
|
||||||
|
return wrapped_func
|
||||||
|
|
||||||
|
def get_or(self, default):
|
||||||
|
return self._value
|
||||||
|
|
||||||
|
def map_left(self, func) -> "Right":
|
||||||
|
return self
|
||||||
|
|
||||||
|
def swap(self) -> "Left":
|
||||||
|
return Left(self._value)
|
||||||
|
|
||||||
|
def is_right(self) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"Right({self._value!r})"
|
||||||
|
|
||||||
|
def __eq__(self, other: object) -> bool:
|
||||||
|
return isinstance(other, Right) and self._value == other._value
|
||||||
|
|
||||||
|
def __hash__(self) -> int:
|
||||||
|
return hash(("Right", self._value))
|
||||||
|
|
||||||
|
|
||||||
|
class Left(Either):
|
||||||
|
__slots__ = ("_error",)
|
||||||
|
|
||||||
|
def __init__(self, error):
|
||||||
|
self._error = error
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def pure(cls, value) -> "Right":
|
||||||
|
return Right(value)
|
||||||
|
|
||||||
|
def bind(self, func) -> "Left":
|
||||||
|
return self
|
||||||
|
|
||||||
|
def fmap(self, func) -> "Left":
|
||||||
|
return self
|
||||||
|
|
||||||
|
def apply(self, wrapped_func: Applicative) -> "Left":
|
||||||
|
return self
|
||||||
|
|
||||||
|
def get_or(self, default):
|
||||||
|
return default
|
||||||
|
|
||||||
|
def map_left(self, func) -> "Left":
|
||||||
|
return Left(func(self._error))
|
||||||
|
|
||||||
|
def swap(self) -> "Right":
|
||||||
|
return Right(self._error)
|
||||||
|
|
||||||
|
def is_right(self) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"Left({self._error!r})"
|
||||||
|
|
||||||
|
def __eq__(self, other: object) -> bool:
|
||||||
|
return isinstance(other, Left) and self._error == other._error
|
||||||
|
|
||||||
|
def __hash__(self) -> int:
|
||||||
|
return hash(("Left", self._error))
|
||||||
|
|
||||||
|
|
||||||
|
class List(Monad):
|
||||||
|
__slots__ = ("_values",)
|
||||||
|
|
||||||
|
def __init__(self, values=None):
|
||||||
|
self._values = list(values) if values is not None else []
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def pure(cls, value) -> "List":
|
||||||
|
return cls([value])
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def empty(cls) -> "List":
|
||||||
|
return cls([])
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def range(cls, *args) -> "List":
|
||||||
|
return cls(range(*args))
|
||||||
|
|
||||||
|
def bind(self, func) -> "List":
|
||||||
|
result = []
|
||||||
|
for v in self._values:
|
||||||
|
out = func(v)
|
||||||
|
if isinstance(out, List):
|
||||||
|
result.extend(out._values)
|
||||||
|
else:
|
||||||
|
result.append(out)
|
||||||
|
return List(result)
|
||||||
|
|
||||||
|
def fmap(self, func) -> "List":
|
||||||
|
return List(func(v) for v in self._values)
|
||||||
|
|
||||||
|
def apply(self, wrapped_func: "List") -> "List":
|
||||||
|
return wrapped_func.bind(lambda f: self.fmap(f))
|
||||||
|
|
||||||
|
def filter(self, predicate) -> "List":
|
||||||
|
return List(v for v in self._values if predicate(v))
|
||||||
|
|
||||||
|
def concat(self, other: "List") -> "List":
|
||||||
|
return List(self._values + other._values)
|
||||||
|
|
||||||
|
def head(self) -> Maybe:
|
||||||
|
return Just(self._values[0]) if self._values else Nothing()
|
||||||
|
|
||||||
|
def tail(self) -> "List":
|
||||||
|
return List(self._values[1:])
|
||||||
|
|
||||||
|
def last(self) -> Maybe:
|
||||||
|
return Just(self._values[-1]) if self._values else Nothing()
|
||||||
|
|
||||||
|
def take(self, n: int) -> "List":
|
||||||
|
return List(self._values[:n])
|
||||||
|
|
||||||
|
def drop(self, n: int) -> "List":
|
||||||
|
return List(self._values[n:])
|
||||||
|
|
||||||
|
def zip_with(self, other: "List", func) -> "List":
|
||||||
|
return List(func(a, b) for a, b in zip(self._values, other._values))
|
||||||
|
|
||||||
|
def fold(self, func, initial):
|
||||||
|
acc = initial
|
||||||
|
for v in self._values:
|
||||||
|
acc = func(acc, v)
|
||||||
|
return acc
|
||||||
|
|
||||||
|
def traverse_maybe(self, func) -> Maybe:
|
||||||
|
results = []
|
||||||
|
for v in self._values:
|
||||||
|
m = func(v)
|
||||||
|
if m.is_nothing():
|
||||||
|
return Nothing()
|
||||||
|
results.append(m.get_or(None))
|
||||||
|
return Just(List(results))
|
||||||
|
|
||||||
|
def sequence_maybe(self) -> Maybe:
|
||||||
|
return self.traverse_maybe(lambda x: x)
|
||||||
|
|
||||||
|
def traverse_either(self, func) -> Either:
|
||||||
|
results = []
|
||||||
|
for v in self._values:
|
||||||
|
e = func(v)
|
||||||
|
if e.is_left():
|
||||||
|
return e
|
||||||
|
results.append(e.get_or(None))
|
||||||
|
return Right(List(results))
|
||||||
|
|
||||||
|
def sequence_either(self) -> Either:
|
||||||
|
return self.traverse_either(lambda x: x)
|
||||||
|
|
||||||
|
def __len__(self) -> int:
|
||||||
|
return len(self._values)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter(self._values)
|
||||||
|
|
||||||
|
def __getitem__(self, index):
|
||||||
|
return self._values[index]
|
||||||
|
|
||||||
|
def __contains__(self, item) -> bool:
|
||||||
|
return item in self._values
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"List({self._values!r})"
|
||||||
|
|
||||||
|
def __eq__(self, other: object) -> bool:
|
||||||
|
return isinstance(other, List) and self._values == other._values
|
||||||
|
|
||||||
|
def __hash__(self) -> int:
|
||||||
|
return hash(("List", tuple(self._values)))
|
||||||
|
|
||||||
|
def __add__(self, other: "List") -> "List":
|
||||||
|
return self.concat(other)
|
||||||
|
|
||||||
|
|
||||||
|
class IO(Monad):
|
||||||
|
__slots__ = ("_thunk",)
|
||||||
|
|
||||||
|
def __init__(self, thunk):
|
||||||
|
self._thunk = thunk
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def pure(cls, value) -> "IO":
|
||||||
|
return cls(lambda: value)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_callable(cls, func, *args, **kwargs) -> "IO":
|
||||||
|
return cls(lambda: func(*args, **kwargs))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def sequence(cls, ios) -> "IO":
|
||||||
|
def run_all():
|
||||||
|
return List([io.run() for io in ios])
|
||||||
|
return cls(run_all)
|
||||||
|
|
||||||
|
def bind(self, func) -> "IO":
|
||||||
|
return IO(lambda: func(self._thunk()).run())
|
||||||
|
|
||||||
|
def fmap(self, func) -> "IO":
|
||||||
|
return IO(lambda: func(self._thunk()))
|
||||||
|
|
||||||
|
def apply(self, wrapped_func: "IO") -> "IO":
|
||||||
|
return IO(lambda: wrapped_func._thunk()(self._thunk()))
|
||||||
|
|
||||||
|
def then(self, other: "IO") -> "IO":
|
||||||
|
return IO(lambda: (self._thunk(), other.run())[1])
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
return self._thunk()
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return "IO(<thunk>)"
|
||||||
|
|
||||||
|
|
||||||
|
class Writer(Monad):
|
||||||
|
__slots__ = ("_value", "_log")
|
||||||
|
|
||||||
|
def __init__(self, value, log: Monoid):
|
||||||
|
self._value = value
|
||||||
|
self._log = log
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def pure(cls, value) -> "Writer":
|
||||||
|
return cls(value, ListMonoid([]))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tell(cls, log: Monoid) -> "Writer":
|
||||||
|
return cls(None, log)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def with_log(cls, value, log: Monoid) -> "Writer":
|
||||||
|
return cls(value, log)
|
||||||
|
|
||||||
|
def bind(self, func) -> "Writer":
|
||||||
|
new_writer = func(self._value)
|
||||||
|
return Writer(new_writer._value, self._log.combine(new_writer._log))
|
||||||
|
|
||||||
|
def fmap(self, func) -> "Writer":
|
||||||
|
return Writer(func(self._value), self._log)
|
||||||
|
|
||||||
|
def apply(self, wrapped_func: "Writer") -> "Writer":
|
||||||
|
return Writer(
|
||||||
|
wrapped_func._value(self._value),
|
||||||
|
self._log.combine(wrapped_func._log),
|
||||||
|
)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
return (self._value, self._log)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self):
|
||||||
|
return self._value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def log(self):
|
||||||
|
return self._log
|
||||||
|
|
||||||
|
def listen(self) -> "Writer":
|
||||||
|
return Writer((self._value, self._log), self._log)
|
||||||
|
|
||||||
|
def censor(self, func) -> "Writer":
|
||||||
|
return Writer(self._value, func(self._log))
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"Writer({self._value!r}, {self._log!r})"
|
||||||
|
|
||||||
|
def __eq__(self, other: object) -> bool:
|
||||||
|
return (
|
||||||
|
isinstance(other, Writer)
|
||||||
|
and self._value == other._value
|
||||||
|
and self._log == other._log
|
||||||
|
)
|
||||||
|
|
||||||
|
def __hash__(self) -> int:
|
||||||
|
return hash(("Writer", self._value))
|
||||||
|
|
||||||
|
|
||||||
|
class State(Monad):
|
||||||
|
__slots__ = ("_run",)
|
||||||
|
|
||||||
|
def __init__(self, run_func):
|
||||||
|
self._run = run_func
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def pure(cls, value) -> "State":
|
||||||
|
return cls(lambda s: (value, s))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get(cls) -> "State":
|
||||||
|
return cls(lambda s: (s, s))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def put(cls, new_state) -> "State":
|
||||||
|
return cls(lambda _: (None, new_state))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def modify(cls, func) -> "State":
|
||||||
|
return cls(lambda s: (None, func(s)))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def gets(cls, func) -> "State":
|
||||||
|
return cls(lambda s: (func(s), s))
|
||||||
|
|
||||||
|
def bind(self, func) -> "State":
|
||||||
|
def _run(state):
|
||||||
|
value, new_state = self._run(state)
|
||||||
|
return func(value)._run(new_state)
|
||||||
|
return State(_run)
|
||||||
|
|
||||||
|
def fmap(self, func) -> "State":
|
||||||
|
def _run(state):
|
||||||
|
value, new_state = self._run(state)
|
||||||
|
return (func(value), new_state)
|
||||||
|
return State(_run)
|
||||||
|
|
||||||
|
def apply(self, wrapped_func: "State") -> "State":
|
||||||
|
def _run(state):
|
||||||
|
f, state1 = wrapped_func._run(state)
|
||||||
|
value, state2 = self._run(state1)
|
||||||
|
return (f(value), state2)
|
||||||
|
return State(_run)
|
||||||
|
|
||||||
|
def run(self, initial_state):
|
||||||
|
return self._run(initial_state)
|
||||||
|
|
||||||
|
def eval(self, initial_state):
|
||||||
|
return self._run(initial_state)[0]
|
||||||
|
|
||||||
|
def exec(self, initial_state):
|
||||||
|
return self._run(initial_state)[1]
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return "State(<s → (a, s)>)"
|
||||||
|
|
||||||
|
|
||||||
|
class Reader(Monad):
|
||||||
|
__slots__ = ("_run",)
|
||||||
|
|
||||||
|
def __init__(self, run_func):
|
||||||
|
self._run = run_func
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def pure(cls, value) -> "Reader":
|
||||||
|
return cls(lambda _: value)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def ask(cls) -> "Reader":
|
||||||
|
return cls(lambda env: env)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def asks(cls, func) -> "Reader":
|
||||||
|
return cls(lambda env: func(env))
|
||||||
|
|
||||||
|
def bind(self, func) -> "Reader":
|
||||||
|
return Reader(lambda env: func(self._run(env))._run(env))
|
||||||
|
|
||||||
|
def fmap(self, func) -> "Reader":
|
||||||
|
return Reader(lambda env: func(self._run(env)))
|
||||||
|
|
||||||
|
def apply(self, wrapped_func: "Reader") -> "Reader":
|
||||||
|
return Reader(lambda env: wrapped_func._run(env)(self._run(env)))
|
||||||
|
|
||||||
|
def local(self, func) -> "Reader":
|
||||||
|
return Reader(lambda env: self._run(func(env)))
|
||||||
|
|
||||||
|
def run(self, env):
|
||||||
|
return self._run(env)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return "Reader(<env → a>)"
|
||||||
|
|
||||||
|
|
||||||
|
class MaybeT(MonadTransformer):
|
||||||
|
__slots__ = ("_inner",)
|
||||||
|
|
||||||
|
def __init__(self, inner: Monad):
|
||||||
|
self._inner = inner
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def pure(cls, value) -> "MaybeT":
|
||||||
|
raise NotImplementedError("Use MaybeT.pure_with(base_cls, value)")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def pure_with(cls, base_cls, value) -> "MaybeT":
|
||||||
|
return cls(base_cls.pure(Just(value)))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def nothing_with(cls, base_cls) -> "MaybeT":
|
||||||
|
return cls(base_cls.pure(Nothing()))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def lift(cls, monad: Monad) -> "MaybeT":
|
||||||
|
return cls(monad.fmap(Just))
|
||||||
|
|
||||||
|
def bind(self, func) -> "MaybeT":
|
||||||
|
def _step(maybe_value):
|
||||||
|
if maybe_value.is_nothing():
|
||||||
|
return self._inner.__class__.pure(Nothing())
|
||||||
|
return func(maybe_value.get_or(None))._inner
|
||||||
|
return MaybeT(self._inner.bind(_step))
|
||||||
|
|
||||||
|
def fmap(self, func) -> "MaybeT":
|
||||||
|
return MaybeT(self._inner.fmap(lambda m: m.fmap(func)))
|
||||||
|
|
||||||
|
def apply(self, wrapped_func: "MaybeT") -> "MaybeT":
|
||||||
|
return wrapped_func.bind(lambda f: self.fmap(f))
|
||||||
|
|
||||||
|
def or_else(self, alternative: "MaybeT") -> "MaybeT":
|
||||||
|
def _step(maybe_value):
|
||||||
|
return self._inner.__class__.pure(maybe_value) if maybe_value.is_just() else alternative._inner
|
||||||
|
return MaybeT(self._inner.bind(_step))
|
||||||
|
|
||||||
|
def run(self) -> Monad:
|
||||||
|
return self._inner
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"MaybeT({self._inner!r})"
|
||||||
|
|
||||||
|
def __rshift__(self, func) -> "MaybeT":
|
||||||
|
return self.bind(func)
|
||||||
291
lib/typeclasses.py
Normal file
291
lib/typeclasses.py
Normal file
|
|
@ -0,0 +1,291 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
|
|
||||||
|
class TypeVar:
|
||||||
|
_registry: dict = {}
|
||||||
|
|
||||||
|
def __init__(self, name: str, *constraints, bound=None,
|
||||||
|
covariant: bool = False, contravariant: bool = False):
|
||||||
|
if covariant and contravariant:
|
||||||
|
raise ValueError("the type var is both covariant and contravariant. impossible!!!!!!!!!!!!")
|
||||||
|
self.name = name
|
||||||
|
self.constraints = constraints
|
||||||
|
self.bound = bound
|
||||||
|
self.covariant = covariant
|
||||||
|
self.contravariant = contravariant
|
||||||
|
TypeVar._registry[name] = self
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
extras = []
|
||||||
|
if self.bound:
|
||||||
|
extras.append(f"bound={self.bound!r}")
|
||||||
|
if self.covariant:
|
||||||
|
extras.append("covariant=True")
|
||||||
|
if self.contravariant:
|
||||||
|
extras.append("contravariant=True")
|
||||||
|
if self.constraints:
|
||||||
|
args = ", ".join(repr(c) for c in self.constraints)
|
||||||
|
return f"TypeVar({self.name!r}, {args})"
|
||||||
|
suffix = ", ".join(extras)
|
||||||
|
return f"TypeVar({self.name!r}{', ' + suffix if suffix else ''})"
|
||||||
|
|
||||||
|
def __class_getitem__(cls, item):
|
||||||
|
return cls
|
||||||
|
|
||||||
|
|
||||||
|
class _GenericAlias:
|
||||||
|
__slots__ = ("__origin__", "__args__", "__parameters__")
|
||||||
|
|
||||||
|
def __init__(self, origin, args):
|
||||||
|
self.__origin__ = origin
|
||||||
|
self.__args__ = args if isinstance(args, tuple) else (args,)
|
||||||
|
self.__parameters__ = tuple(a for a in self.__args__ if isinstance(a, TypeVar))
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
def _name(a):
|
||||||
|
return a.__name__ if hasattr(a, "__name__") else repr(a)
|
||||||
|
return f"{self.__origin__.__name__}[{', '.join(_name(a) for a in self.__args__)}]"
|
||||||
|
|
||||||
|
def __call__(self, *args, **kwargs):
|
||||||
|
return self.__origin__(*args, **kwargs)
|
||||||
|
|
||||||
|
def __instancecheck__(self, instance):
|
||||||
|
return isinstance(instance, self.__origin__)
|
||||||
|
|
||||||
|
def __subclasscheck__(self, subclass):
|
||||||
|
return issubclass(subclass, self.__origin__)
|
||||||
|
|
||||||
|
def __class_getitem__(cls, params):
|
||||||
|
return cls
|
||||||
|
|
||||||
|
def __getitem__(self, params):
|
||||||
|
return _GenericAlias(self.__origin__, params)
|
||||||
|
|
||||||
|
|
||||||
|
class Generic:
|
||||||
|
def __class_getitem__(cls, params):
|
||||||
|
return _GenericAlias(cls, params)
|
||||||
|
|
||||||
|
def __init_subclass__(cls, **kwargs):
|
||||||
|
super().__init_subclass__(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
A = TypeVar("A")
|
||||||
|
B = TypeVar("B")
|
||||||
|
C = TypeVar("C")
|
||||||
|
S = TypeVar("S")
|
||||||
|
W = TypeVar("W")
|
||||||
|
R = TypeVar("R")
|
||||||
|
E = TypeVar("E")
|
||||||
|
F = TypeVar("F")
|
||||||
|
|
||||||
|
|
||||||
|
class Semigroup(ABC):
|
||||||
|
@abstractmethod
|
||||||
|
def combine(self, other: "Semigroup") -> "Semigroup":
|
||||||
|
...
|
||||||
|
|
||||||
|
def __add__(self, other: "Semigroup") -> "Semigroup":
|
||||||
|
return self.combine(other)
|
||||||
|
|
||||||
|
|
||||||
|
class Monoid(Semigroup, ABC):
|
||||||
|
@classmethod
|
||||||
|
@abstractmethod
|
||||||
|
def empty(cls) -> "Monoid":
|
||||||
|
...
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def concat(cls, xs) -> "Monoid":
|
||||||
|
result = cls.empty()
|
||||||
|
for x in xs:
|
||||||
|
result = result.combine(x)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class ListMonoid(Monoid):
|
||||||
|
def __init__(self, values=None):
|
||||||
|
self.values = list(values) if values is not None else []
|
||||||
|
|
||||||
|
def combine(self, other: "ListMonoid") -> "ListMonoid":
|
||||||
|
if not isinstance(other, ListMonoid):
|
||||||
|
raise TypeError(f"Cannot combine ListMonoid with {type(other).__name__}")
|
||||||
|
return ListMonoid(self.values + other.values)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def empty(cls) -> "ListMonoid":
|
||||||
|
return cls([])
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"ListMonoid({self.values!r})"
|
||||||
|
|
||||||
|
def __eq__(self, other: object) -> bool:
|
||||||
|
return isinstance(other, ListMonoid) and self.values == other.values
|
||||||
|
|
||||||
|
def __hash__(self) -> int:
|
||||||
|
return hash(("ListMonoid", tuple(self.values)))
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter(self.values)
|
||||||
|
|
||||||
|
def __len__(self) -> int:
|
||||||
|
return len(self.values)
|
||||||
|
|
||||||
|
|
||||||
|
class StringMonoid(Monoid):
|
||||||
|
def __init__(self, value: str = ""):
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
def combine(self, other: "StringMonoid") -> "StringMonoid":
|
||||||
|
if not isinstance(other, StringMonoid):
|
||||||
|
raise TypeError(f"Cannot combine StringMonoid with {type(other).__name__}")
|
||||||
|
return StringMonoid(self.value + other.value)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def empty(cls) -> "StringMonoid":
|
||||||
|
return cls("")
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"StringMonoid({self.value!r})"
|
||||||
|
|
||||||
|
def __eq__(self, other: object) -> bool:
|
||||||
|
return isinstance(other, StringMonoid) and self.value == other.value
|
||||||
|
|
||||||
|
def __hash__(self) -> int:
|
||||||
|
return hash(("StringMonoid", self.value))
|
||||||
|
|
||||||
|
|
||||||
|
class SumMonoid(Monoid):
|
||||||
|
def __init__(self, value=0):
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
def combine(self, other: "SumMonoid") -> "SumMonoid":
|
||||||
|
return SumMonoid(self.value + other.value)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def empty(cls) -> "SumMonoid":
|
||||||
|
return cls(0)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"Sum({self.value})"
|
||||||
|
|
||||||
|
def __eq__(self, other: object) -> bool:
|
||||||
|
return isinstance(other, SumMonoid) and self.value == other.value
|
||||||
|
|
||||||
|
def __hash__(self) -> int:
|
||||||
|
return hash(("Sum", self.value))
|
||||||
|
|
||||||
|
|
||||||
|
class ProductMonoid(Monoid):
|
||||||
|
def __init__(self, value=1):
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
def combine(self, other: "ProductMonoid") -> "ProductMonoid":
|
||||||
|
return ProductMonoid(self.value * other.value)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def empty(cls) -> "ProductMonoid":
|
||||||
|
return cls(1)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"Product({self.value})"
|
||||||
|
|
||||||
|
def __eq__(self, other: object) -> bool:
|
||||||
|
return isinstance(other, ProductMonoid) and self.value == other.value
|
||||||
|
|
||||||
|
def __hash__(self) -> int:
|
||||||
|
return hash(("Product", self.value))
|
||||||
|
|
||||||
|
|
||||||
|
class Functor(ABC, Generic):
|
||||||
|
@abstractmethod
|
||||||
|
def fmap(self, func) -> "Functor":
|
||||||
|
...
|
||||||
|
|
||||||
|
def __mod__(self, func) -> "Functor":
|
||||||
|
return self.fmap(func)
|
||||||
|
|
||||||
|
|
||||||
|
class Applicative(Functor, ABC):
|
||||||
|
@classmethod
|
||||||
|
@abstractmethod
|
||||||
|
def pure(cls, value) -> "Applicative":
|
||||||
|
...
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def apply(self, wrapped_func: "Applicative") -> "Applicative":
|
||||||
|
...
|
||||||
|
|
||||||
|
def map2(self, other: "Applicative", func) -> "Applicative":
|
||||||
|
return other.apply(self.fmap(lambda a: lambda b: func(a, b)))
|
||||||
|
|
||||||
|
def __mul__(self, wrapped_func: "Applicative") -> "Applicative":
|
||||||
|
return self.apply(wrapped_func)
|
||||||
|
|
||||||
|
|
||||||
|
class Monad(Applicative, ABC):
|
||||||
|
@abstractmethod
|
||||||
|
def bind(self, func) -> "Monad":
|
||||||
|
...
|
||||||
|
|
||||||
|
def fmap(self, func) -> "Monad":
|
||||||
|
return self.bind(lambda x: self.__class__.pure(func(x)))
|
||||||
|
|
||||||
|
def apply(self, wrapped_func: "Applicative") -> "Monad":
|
||||||
|
return wrapped_func.bind(
|
||||||
|
lambda f: self.bind(lambda x: self.__class__.pure(f(x)))
|
||||||
|
)
|
||||||
|
|
||||||
|
def then(self, other: "Monad") -> "Monad":
|
||||||
|
return self.bind(lambda _: other)
|
||||||
|
|
||||||
|
def __rshift__(self, func) -> "Monad":
|
||||||
|
return self.bind(func)
|
||||||
|
|
||||||
|
def __or__(self, other: "Monad") -> "Monad":
|
||||||
|
return self.then(other)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def do(cls, gen_func) -> "Monad":
|
||||||
|
return _drive_do(gen_func)
|
||||||
|
|
||||||
|
|
||||||
|
def _drive_do(gen_func) -> "Monad":
|
||||||
|
gen = gen_func()
|
||||||
|
|
||||||
|
def step(value):
|
||||||
|
try:
|
||||||
|
next_monad = gen.send(value)
|
||||||
|
return next_monad.bind(step)
|
||||||
|
except StopIteration as exc:
|
||||||
|
return exc.value
|
||||||
|
|
||||||
|
try:
|
||||||
|
first = next(gen)
|
||||||
|
return first.bind(step)
|
||||||
|
except StopIteration as exc:
|
||||||
|
return exc.value
|
||||||
|
|
||||||
|
|
||||||
|
class MonadTransformer(ABC):
|
||||||
|
@classmethod
|
||||||
|
@abstractmethod
|
||||||
|
def lift(cls, monad: "Monad") -> "MonadTransformer":
|
||||||
|
...
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def run(self) -> "Monad":
|
||||||
|
...
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def bind(self, func) -> "MonadTransformer":
|
||||||
|
...
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
@abstractmethod
|
||||||
|
def pure(cls, value) -> "MonadTransformer":
|
||||||
|
...
|
||||||
|
|
||||||
|
def __rshift__(self, func) -> "MonadTransformer":
|
||||||
|
return self.bind(func)
|
||||||
4
test.py
Normal file
4
test.py
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
from lib import List
|
||||||
|
|
||||||
|
lst = List.pure(2) >> (lambda n: List([n, n+1]))
|
||||||
|
print(lst)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue