Skip to content

State

funstruct.monad.state

State monad — pure stateful computation without mutation.

Examples:

>>> from funstruct.monad.state import State
>>> inc = State(lambda s: (s + 1, s))
>>> inc.run(0)
(1, 0)
>>> (inc >> (lambda _: inc)).run(0)
(2, 1)
>>> State.pure(42).run(99)
(99, 42)

State

Bases: DataType, Generic[_A]

Pure State monad: S -> (S, A).

Source code in funstruct/monad/state/__init__.py
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
class State(DataType, Generic[_A]):
    """Pure State monad: ``S -> (S, A)``."""

    def __init__(self, run: Callable[[Any], tuple[Any, _A]]) -> None:
        self._run = run

    def run(self, initial_state) -> tuple:
        """Execute with initial state. Returns ``(final_state, value)``.

        >>> State.pure(10).run("any")
        ('any', 10)
        """
        return self._run(initial_state)

    def bind(self, f: Callable[[_A], State[_B]]) -> State[_B]:
        """>>> State.pure(1).bind(lambda x: State.pure(x + 10)).run(0)
        (0, 11)
        """

        def inner(s):
            new_s, a = self._run(s)
            return f(a).run(new_s)

        return State(inner)

    @classmethod
    def do(cls, gen_fn) -> Callable[..., State]:
        """Do-notation via generators. Returns a callable.

        >>> def pipeline():
        ...     x = yield State(lambda s: (s + 1, s))
        ...     y = yield State(lambda s: (s + 1, s))
        ...     return x + y
        >>> State.do(pipeline)().run(0)
        (2, 1)
        """

        def _thunk(*args, **kwargs):
            def _run(s):
                gen = gen_fn(*args, **kwargs)
                try:
                    monadic_val = next(gen)
                    while True:
                        new_s, result = monadic_val.run(s)
                        s = new_s
                        monadic_val = gen.send(result)
                except StopIteration as e:
                    return (s, e.value)

            return cls(_run)

        return _thunk

    @classmethod
    def pure(cls, value) -> State:
        """Lift a value without modifying state.

        >>> State.pure("hello").run(99)
        (99, 'hello')
        """
        return cls(lambda s: (s, value))

    @classmethod
    def get(cls) -> State:
        """Produce current state as the value.

        >>> State.get().run(42)
        (42, 42)
        """
        return cls(lambda s: (s, s))

    @classmethod
    def modify(cls, f: Callable[[Any], Any]) -> State:
        """Modify state, produce None.

        >>> State.modify(lambda s: s + 1).run(5)
        (6, None)
        """
        return cls(lambda s: (f(s), None))

    def __repr__(self) -> str:
        return f"State({self._run})"

run(initial_state)

Execute with initial state. Returns (final_state, value).

State.pure(10).run("any") ('any', 10)

Source code in funstruct/monad/state/__init__.py
31
32
33
34
35
36
37
def run(self, initial_state) -> tuple:
    """Execute with initial state. Returns ``(final_state, value)``.

    >>> State.pure(10).run("any")
    ('any', 10)
    """
    return self._run(initial_state)

bind(f)

State.pure(1).bind(lambda x: State.pure(x + 10)).run(0) (0, 11)

Source code in funstruct/monad/state/__init__.py
39
40
41
42
43
44
45
46
47
48
def bind(self, f: Callable[[_A], State[_B]]) -> State[_B]:
    """>>> State.pure(1).bind(lambda x: State.pure(x + 10)).run(0)
    (0, 11)
    """

    def inner(s):
        new_s, a = self._run(s)
        return f(a).run(new_s)

    return State(inner)

do(gen_fn) classmethod

Do-notation via generators. Returns a callable.

def pipeline(): ... x = yield State(lambda s: (s + 1, s)) ... y = yield State(lambda s: (s + 1, s)) ... return x + y State.do(pipeline)().run(0) (2, 1)

Source code in funstruct/monad/state/__init__.py
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
@classmethod
def do(cls, gen_fn) -> Callable[..., State]:
    """Do-notation via generators. Returns a callable.

    >>> def pipeline():
    ...     x = yield State(lambda s: (s + 1, s))
    ...     y = yield State(lambda s: (s + 1, s))
    ...     return x + y
    >>> State.do(pipeline)().run(0)
    (2, 1)
    """

    def _thunk(*args, **kwargs):
        def _run(s):
            gen = gen_fn(*args, **kwargs)
            try:
                monadic_val = next(gen)
                while True:
                    new_s, result = monadic_val.run(s)
                    s = new_s
                    monadic_val = gen.send(result)
            except StopIteration as e:
                return (s, e.value)

        return cls(_run)

    return _thunk

pure(value) classmethod

Lift a value without modifying state.

State.pure("hello").run(99) (99, 'hello')

Source code in funstruct/monad/state/__init__.py
78
79
80
81
82
83
84
85
@classmethod
def pure(cls, value) -> State:
    """Lift a value without modifying state.

    >>> State.pure("hello").run(99)
    (99, 'hello')
    """
    return cls(lambda s: (s, value))

get() classmethod

Produce current state as the value.

State.get().run(42) (42, 42)

Source code in funstruct/monad/state/__init__.py
87
88
89
90
91
92
93
94
@classmethod
def get(cls) -> State:
    """Produce current state as the value.

    >>> State.get().run(42)
    (42, 42)
    """
    return cls(lambda s: (s, s))

modify(f) classmethod

Modify state, produce None.

State.modify(lambda s: s + 1).run(5) (6, None)

Source code in funstruct/monad/state/__init__.py
 96
 97
 98
 99
100
101
102
103
@classmethod
def modify(cls, f: Callable[[Any], Any]) -> State:
    """Modify state, produce None.

    >>> State.modify(lambda s: s + 1).run(5)
    (6, None)
    """
    return cls(lambda s: (f(s), None))