Coverage for greyhorse / river / private / monads / rsr.py: 97%
33 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: warn_no_return=false,disable_error_code="arg-type,return-value"
2from __future__ import annotations
4from collections.abc import Callable
6from greyhorse.result import Err, Ok, Result
9class RSR[R, S, T, E]:
10 """Reader + State + Result monad — environment, state, and fallible computation.
12 Wraps ``(R, S) -> (Result[T, E], S)``. Short-circuits on ``Err``, threads state.
14 Type parameters:
15 R: Environment type.
16 S: State type.
17 T: Success value type.
18 E: Error type.
19 """
21 __slots__ = ('_run',)
23 def __init__(self, function: Callable[[R, S], tuple[Result[T, E], S]]) -> None:
24 self._run = function
26 def map[U](self, f: Callable[[T], U]) -> RSR[R, S, U, E]:
27 """Apply a pure function to the success value, preserving state."""
28 def run(r: R, s: S) -> tuple[Result[U, E], S]:
29 a, s = self._run(r, s)
30 return a.map(f), s
32 return RSR(run)
34 def and_then[U](self, f: Callable[[T], RSR[R, S, U, E]]) -> RSR[R, S, U, E]:
35 """Monadic bind — chain on success, short-circuit on error."""
36 def run(r: R, s: S) -> tuple[Result[U, E], S]:
37 a, s1 = self._run(r, s)
38 match a:
39 case Ok(v):
40 b, s2 = f(v)(r, s1)
41 return b, s2
42 case Err(_):
43 return a, s1
45 return RSR(run)
47 def __call__(self, r: R, state: S) -> tuple[Result[T, E], S]:
48 """Execute the computation with environment and initial state."""
49 return self._run(r, state)
51 @staticmethod
52 def pure(value: Result[T, E]) -> RSR[R, S, T, E]:
53 """Wrap a Result value, passing state through unchanged."""
54 return RSR(lambda r, s: (value, s))
56 @staticmethod
57 def get() -> RSR[R, S, T, E]:
58 """Return the current state as value."""
59 return RSR(lambda r, s: (s, s))
61 @staticmethod
62 def put(s: S) -> RSR[R, S, None, E]:
63 """Replace the current state."""
64 return RSR(lambda r, _: (Ok(), s))