Coverage for greyhorse / river / private / monads / effect.py: 100%
23 statements
« prev ^ index » next coverage.py v7.14.0, created at 2026-05-18 11:33 +0300
« prev ^ index » next coverage.py v7.14.0, created at 2026-05-18 11:33 +0300
1# mypy: disable_error_code="misc,arg-type"
2"""Effect protocol and ExitCase enum — foundation of the monadic algebra.
4``Effect[T]`` is the protocol for effectful computations with bracket semantics.
5``ExitCase`` communicates how a bracketed use-phase ended.
6"""
8from __future__ import annotations
10import asyncio
11from collections.abc import Callable
12from typing import Protocol
14from greyhorse.enum import Enum, Struct, Unit
17class ExitCase(Enum):
18 """Outcome of a bracketed computation.
20 Used by ``bracket_case`` to communicate how the ``use`` phase ended,
21 so the release function can decide cleanup strategy.
23 Variants:
24 Succeeded: The use-phase completed normally.
25 Canceled: The use-phase was canceled (``asyncio.CancelledError``).
26 Errored(exc): The use-phase raised an exception.
28 Examples:
29 ::
31 ExitCase.from_exc(None) # Succeeded
32 ExitCase.from_exc(asyncio.CancelledError()) # Canceled
33 ExitCase.from_exc(ValueError('x')) # Errored(exc=ValueError('x'))
35 Pattern matching::
37 match exit_case:
38 case ExitCase.Succeeded:
39 commit()
40 case ExitCase.Errored(exc=e):
41 rollback(e)
42 case ExitCase.Canceled:
43 rollback(None)
44 """
46 Succeeded = Unit()
47 Canceled = Unit()
48 Errored = Struct(exc=Exception)
50 @staticmethod
51 def from_exc(exc: BaseException | None) -> ExitCase:
52 """Create an ExitCase from an exception (or None for success).
54 Args:
55 exc: The exception, or None if the computation succeeded.
57 Returns:
58 ``Succeeded`` if exc is None, ``Canceled`` for ``CancelledError``,
59 ``Errored(exc=...)`` otherwise.
60 """
61 if exc is None:
62 return ExitCase.Succeeded
63 if isinstance(exc, asyncio.CancelledError):
64 return ExitCase.Canceled
65 return ExitCase.Errored(exc=exc)
68class Effect[T](Protocol):
69 """Protocol for effectful computations with bracket semantics.
71 Implementations must provide ``pure``, ``and_then``, and ``bracket_case``.
72 ``IO`` is the canonical synchronous implementation.
74 Type parameters:
75 T: The result type of the computation.
76 """
78 @staticmethod
79 def pure(value: T) -> Effect[T]:
80 """Wrap a pure value into this Effect (no side effects)."""
81 ...
83 def and_then[U](self, f: Callable[[T], Effect[U]]) -> Effect[U]:
84 """Monadic bind — chain with a function returning Effect."""
85 ...
87 def bracket[U](
88 self, use: Callable[[T], Effect[U]], release: Callable[[T], Effect[None]]
89 ) -> Effect[U]:
90 """Acquire-use-release with simple release (no ExitCase info).
92 Args:
93 use: Function applied to the acquired value.
94 release: Cleanup function (called regardless of outcome).
96 Returns:
97 Effect that runs acquire → use → release.
98 """
99 return self.bracket_case(use, lambda v, _: release(v))
101 def bracket_case[U](
102 self, use: Callable[[T], Effect[U]], release: Callable[[T, ExitCase], Effect[None]]
103 ) -> Effect[U]:
104 """Acquire-use-release with ExitCase feedback to the release function.
106 Args:
107 use: Function applied to the acquired value.
108 release: Cleanup function ``(value, exit_case) -> Effect[None]``.
110 Returns:
111 Effect that runs acquire → use → release with ExitCase.
112 """
113 ...