Coverage for greyhorse / river / private / monads / rs.py: 100%
28 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
1from __future__ import annotations
3from collections.abc import Callable
6class RS[R, S, T]:
7 """Reader + State monad — computation with environment and mutable state.
9 Wraps a function ``(R, S) -> (T, S)``. Threads state through the chain.
11 Type parameters:
12 R: Environment type.
13 S: State type.
14 T: Result type.
16 Examples:
17 ::
19 rs = RS(lambda r, s: (r + s, s + 1))
20 assert rs(10, 5) == (15, 6)
22 chained = rs.and_then(lambda v: RS(lambda r, s: (v * 10, s + 1)))
23 assert chained(2, 3) == (50, 5) # (2+3=5, s=4) → (5*10=50, s=5)
24 """
26 __slots__ = ('_run',)
28 def __init__(self, function: Callable[[R, S], tuple[T, S]]) -> None:
29 self._run = function
31 def map[U](self, f: Callable[[T], U]) -> RS[R, S, U]:
32 """Apply a pure function to the result, preserving state."""
33 def run(r: R, s: S) -> tuple[U, S]:
34 a, s = self._run(r, s)
35 return f(a), s
37 return RS(run)
39 def and_then[U](self, f: Callable[[T], RS[R, S, U]]) -> RS[R, S, U]:
40 """Monadic bind — chain, threading state through both steps."""
41 def run(r: R, s: S) -> tuple[U, S]:
42 a, s1 = self._run(r, s)
43 b, s2 = f(a)(r, s1)
44 return b, s2
46 return RS(run)
48 def __call__(self, r: R, state: S) -> tuple[T, S]:
49 """Execute the computation with environment and initial state."""
50 return self._run(r, state)
52 @staticmethod
53 def pure(value: T) -> RS[R, S, T]:
54 """Wrap a pure value, passing state through unchanged."""
55 return RS(lambda r, s: (value, s))
57 @staticmethod
58 def get() -> RS[R, S, T]:
59 """Return the current state as value."""
60 return RS(lambda r, s: (s, s))
62 @staticmethod
63 def put(s: S) -> RS[R, S, None]:
64 """Replace the current state."""
65 return RS(lambda r, _: (None, s))