From 8eb63a0625ce3345a20b77377da1e3aad4cbdd87 Mon Sep 17 00:00:00 2001 From: laniakea Date: Fri, 10 Apr 2026 00:21:44 +0200 Subject: [PATCH] push --- .gitignore | 2 + lib/__init__.py | 44 +++ lib/classmethod.py | 30 +++ lib/monads.py | 649 +++++++++++++++++++++++++++++++++++++++++++++ lib/typeclasses.py | 291 ++++++++++++++++++++ test.py | 4 + 6 files changed, 1020 insertions(+) create mode 100644 .gitignore create mode 100644 lib/__init__.py create mode 100644 lib/classmethod.py create mode 100644 lib/monads.py create mode 100644 lib/typeclasses.py create mode 100644 test.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..de35933 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__/ +**/__pycache__/ diff --git a/lib/__init__.py b/lib/__init__.py new file mode 100644 index 0000000..3afd80c --- /dev/null +++ b/lib/__init__.py @@ -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", +] \ No newline at end of file diff --git a/lib/classmethod.py b/lib/classmethod.py new file mode 100644 index 0000000..f53cebc --- /dev/null +++ b/lib/classmethod.py @@ -0,0 +1,30 @@ +from __future__ import annotations + + +class Classmethod: + def __init__(self, func): + self.func = func + self._attribute_name: str = getattr(func, "__name__", "") + + 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 \ No newline at end of file diff --git a/lib/monads.py b/lib/monads.py new file mode 100644 index 0000000..bbe2b14 --- /dev/null +++ b/lib/monads.py @@ -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()" + + +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()" + + +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()" + + +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) \ No newline at end of file diff --git a/lib/typeclasses.py b/lib/typeclasses.py new file mode 100644 index 0000000..15dae1b --- /dev/null +++ b/lib/typeclasses.py @@ -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) \ No newline at end of file diff --git a/test.py b/test.py new file mode 100644 index 0000000..10f98cc --- /dev/null +++ b/test.py @@ -0,0 +1,4 @@ +from lib import List + +lst = List.pure(2) >> (lambda n: List([n, n+1])) +print(lst)