State Monad
Thread state through computations functionally
The State monad represents stateful computations in a pure functional way. Instead of using mutable state, you thread state through a chain of operations explicitly, making state changes visible and predictable.
State[S, A] is a function from state S to a tuple of (value, new_state). The value is the computation result, and the new_state is the updated state.
Creating & Accessing State
Wrapping State Functions
The State constructor wraps a function that receives the current state and returns a value with the new state.
from better_py import State
# A stateful computation
increment = State(lambda count: (count, count + 1))
# Run with initial state
result, new_state = increment.run(0)
result # 0
new_state # 1Use the constructor when you need full control over how state transforms.
Getting Current State
The State.get method returns the current state as both the value and the new state.
from better_py import State
get_state = State.get()
value, state = get_state.run(42)
value # 42
state # 42Use this when you need to read the state without modifying it.
Replacing State
The State.put method replaces the state with a new value.
from better_py import State
set_state = State.put(100)
value, state = set_state.run(0)
value # None
state # 100Use this when you need to completely replace the state.
Modifying State
The State.modify method applies a function to transform the state.
from better_py import State
increment = State.modify(lambda x: x + 1)
value, state = increment.run(5)
value # None
state # 6Use this when you need to update the state based on its current value.
Running State
Getting Value and State
The run method executes the state computation and returns both the result and final state.
from better_py import State
state = State(lambda s: (s * 2, s + 1))
value, new_state = state.run(5)
value # 10
new_state # 6This is the most common way to execute state computations when you need both results.
Getting Only the Value
The eval method returns only the computation result, discarding the final state.
from better_py import State
state = State(lambda s: (s * 2, s + 1))
value = state.eval(5) # 10Use this when you care about the result but not the final state.
Getting Only the Final State
The execute method returns only the final state, discarding the computation result.
from better_py import State
state = State(lambda s: (s * 2, s + 1))
final_state = state.execute(5) # 6Use this when you care about the state transformation but not the intermediate result.
Transforming State
Mapping Values
The map method transforms the computation result without affecting state.
from better_py import State
state = State(lambda s: (s, s + 1))
mapped = state.map(lambda x: x * 2)
value, new_state = mapped.run(5)
value # 10
new_state # 6Use map when you need to transform the result without touching the state.
Chaining Operations
The flat_map method chains state operations, threading state through each step.
from better_py import State
def increment(s):
return (s, s + 1)
def double(s):
return (s * 2, s)
state = State(increment).flat_map(lambda x: State(double))
value, new_state = state.run(5)
value # 6
new_state # 6Use flat_map for sequential operations where later steps depend on earlier results.
Real-World Pattern: Key-Value Store
from better_py import State
def get(key):
return State.get().map(lambda store: store.get(key))
def put(key, value):
return State.modify(lambda store: {**store, key: value})
def delete(key):
return State.modify(lambda store: {k: v for k, v in store.items() if k != key})
# Store operations
operations = (put("name", "Alice")
.flat_map(lambda _: put("age", 30))
.flat_map(lambda _: get("name"))
.flat_map(lambda name: put("greeting", f"Hello, {name}"))
.flat_map(lambda _: get("greeting")))
value, state = operations.run({})
value # "Hello, Alice"
state # {"name": "Alice", "age": 30, "greeting": "Hello, Alice"}This pattern shows State's strength for managing complex state transitions: each operation modifies the store functionally, and the state is threaded through automatically without mutable variables.
When to Use
Use State when:
- You need to manage state functionally
- You want to avoid mutable variables
- You're building pure functional code
- You need to thread state through operations
- You want explicit state management
- You're implementing algorithms with state
Don't use State when:
- Mutable state is acceptable (use plain variables)
- State doesn't change between operations
- You're writing simple scripts
- The state complexity doesn't warrant the abstraction