State Patterns
State patterns are ways to pass changing data through steps without shared variables. Each step receives the current data and returns updated data.
One step returns a new state and a value
The step starts from 10. It returns 11 as the new state and 10 as the value.
class State[S, A](runF: S => (S, A)):
def run(start: S): (S, A) = runF(start)
def map[B](f: A => B): State[S, B] =
State(s => {
val (next, value) = runF(s)
(next, f(value))
})
def flatMap[B](f: A => State[S, B]): State[S, B] =
State(s => {
val (next, value) = runF(s)
f(value).run(next)
})
@main def main(): Unit =
val inc = State[Int, Int](s => (s + 1, s))
val (state, value) = inc.run(10)
println(state)
println(value)Map changes the value only
The step starts from 5 and doubles the value. Mapping adds 100 to that value.
class State[S, A](runF: S => (S, A)):
def run(start: S): (S, A) = runF(start)
def map[B](f: A => B): State[S, B] =
State(s => {
val (next, value) = runF(s)
(next, f(value))
})
def flatMap[B](f: A => State[S, B]): State[S, B] =
State(s => {
val (next, value) = runF(s)
f(value).run(next)
})
@main def main(): Unit =
val step = State[Int, Int](s => (s + 1, s * 2))
val mapped = step.map(v => v + 100)
val (state, value) = mapped.run(5)
println(state)
println(value)FlatMap feeds the new state forward
Two steps run in order. The second step starts from the state left by the first step.
class State[S, A](runF: S => (S, A)):
def run(start: S): (S, A) = runF(start)
def map[B](f: A => B): State[S, B] =
State(s => {
val (next, value) = runF(s)
(next, f(value))
})
def flatMap[B](f: A => State[S, B]): State[S, B] =
State(s => {
val (next, value) = runF(s)
f(value).run(next)
})
@main def main(): Unit =
val step = State[Int, Int](s => (s + 1, s + 1))
val two = step.flatMap(a => step.map(b => a + b))
val (state, value) = two.run(0)
println(state)
println(value)Example
class State[S, A](runF: S => (S, A)):
def run(start: S): (S, A) = runF(start)
def map[B](f: A => B): State[S, B] =
State(s => {
val (next, value) = runF(s)
(next, f(value))
})
def flatMap[B](f: A => State[S, B]): State[S, B] =
State(s => {
val (next, value) = runF(s)
f(value).run(next)
})
@main def main(): Unit =
val countFive = State[Int, Int](s => (s + 5, 5))
val countSeven = State[Int, Int](s => (s + 7, 7))
val program = countFive.flatMap(a => countSeven.map(b => a + b))
val (state, value) = program.run(10)
println(state)
println(value)Key points
- State: data that changes as steps run.
- Use run to start a state program with an initial value.
- Result: a step returns a new state together with a value.
- Use map to change the value while keeping the new state.
- Use flatMap to feed the new state into the next step.
In this example, the first step moves the state from 10 to 15 and returns 5. The second step moves the state from 15 to 22 and returns 7. Adding the two values gives 12.
Note
each step returns a fresh state instead of changing the input.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Scastie
Exercise
A state step starts from 20 and returns (21, 20). What is the new state?