Skip to content

Commit 01f9bf3

Browse files
committed
Rework State monad section
1 parent 79e11e9 commit 01f9bf3

File tree

1 file changed

+41
-35
lines changed

1 file changed

+41
-35
lines changed

src/pages/monads/state.typ

Lines changed: 41 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,46 @@
1-
#import "../stdlib.typ": info, warning, solution
1+
#import "../stdlib.typ": info, warning, exercise, solution
22
== The State Monad
33
<sec:monad:state>
44

55

6-
[`cats.data.State`][cats.data.State]
6+
`cats.data.State`
77
allows us to pass additional state around as part of a computation.
88
We define `State` instances representing atomic state operations
99
and thread them together using `map` and `flatMap`.
1010
In this way we can model mutable state in a purely functional way,
1111
without using actual mutation.
1212

13-
=== Creating and Unpacking State
1413

14+
=== Creating and Unpacking State
1515

1616
Boiled down to their simplest form,
1717
instances of `State[S, A]` represent functions of type `S => (S, A)`.
1818
`S` is the type of the state and `A` is the type of the result.
19+
In the example below, the state has type `Int`,
20+
and we return a `String` result computed from the state.
1921

20-
```scala mdoc:silent
22+
```scala mdoc
2123
import cats.data.State
22-
```
2324
24-
```scala mdoc:silent
2525
val a = State[Int, String]{ state =>
2626
(state, s"The state is $state")
2727
}
2828
```
2929

30+
#info(title: [State and IndexedStateT])[
31+
You may have noticed the console output
32+
reports a type of `IndexedStateT`
33+
when we create an instance of `State`.
34+
Just like with `Writer` and `Reader`,
35+
`State` is defined as a type alias of a more complicated type `IndexedStateT`:
36+
37+
```scala
38+
type State[S, A] = IndexedStateT[Eval, S, S, A]
39+
```
40+
41+
We can ignore this more complicated type.
42+
]
43+
3044
In other words, an instance of `State` is a function
3145
that does two things:
3246

@@ -51,22 +65,16 @@ val justTheState = a.runS(10).value
5165
val justTheResult = a.runA(10).value
5266
```
5367

54-
=== Composing and Transforming State
5568

69+
=== Composing and Transforming State
5670

5771
As we've seen with `Reader` and `Writer`,
5872
the power of the `State` monad comes from combining instances.
5973
The `map` and `flatMap` methods thread the state from one instance to another.
6074
Each individual instance represents an atomic state transformation,
6175
and their combination represents a complete sequence of changes:
6276

63-
```scala mdoc:invisible:reset-object
64-
import cats.data.State
65-
val a = State[Int, String]{ state =>
66-
(state, s"The state is $state")
67-
}
68-
```
69-
```scala mdoc:silent
77+
```scala mdoc:silent:nest
7078
val step1 = State[Int, String]{ num =>
7179
val ans = num + 1
7280
(ans, s"Result of step1: $ans")
@@ -83,15 +91,15 @@ val both = for {
8391
} yield (a, b)
8492
```
8593

94+
When we run this program
95+
we get the result of applying each step in sequence.
96+
State is threaded from step to step
97+
even though we don't interact with it in the for comprehension.
98+
8699
```scala mdoc
87100
val (state, result) = both.run(20).value
88101
```
89102

90-
As you can see, in this example the final state
91-
is the result of applying both transformations in sequence.
92-
State is threaded from step to step
93-
even though we don't interact with it in the for comprehension.
94-
95103
The general model for using the `State` monad
96104
is to represent each step of a computation as an instance
97105
and compose the steps using the standard monad operators.
@@ -104,45 +112,43 @@ Cats provides several convenience constructors for creating primitive steps:
104112
- `modify` updates the state using an update function.
105113

106114
```scala mdoc
107-
val getDemo = State.get[Int]
108-
getDemo.run(10).value
115+
State.get[Int].run(10).value
109116
110-
val setDemo = State.set[Int](30)
111-
setDemo.run(10).value
117+
State.set[Int](30).run(10).value
112118
113-
val pureDemo = State.pure[Int, String]("Result")
114-
pureDemo.run(10).value
119+
State.pure[Int, String]("Result").run(10).value
115120
116-
val inspectDemo = State.inspect[Int, String](x => s"${x}!")
117-
inspectDemo.run(10).value
121+
State.inspect[Int, String](x => s"${x}!").run(10).value
118122
119-
val modifyDemo = State.modify[Int](_ + 1)
120-
modifyDemo.run(10).value
123+
State.modify[Int](_ + 1).run(10).value
121124
```
122125

123126
We can assemble these building blocks using a for comprehension.
124127
We typically ignore the result of intermediate stages
125-
that only represent transformations on the state:
128+
that only represent transformations on the state.
126129

127-
```scala mdoc:silent:reset-object
130+
```scala mdoc:silent:nest
128131
import cats.data.State
129-
import State._
130-
```
132+
import State.*
131133
132-
```scala mdoc
133134
val program: State[Int, (Int, Int, Int)] = for {
134135
a <- get[Int]
135136
_ <- set[Int](a + 1)
136137
b <- get[Int]
137138
_ <- modify[Int](_ + 1)
138139
c <- inspect[Int, Int](_ * 1000)
139140
} yield (a, b, c)
141+
```
140142

143+
As we expect,
144+
the result is the composition of the individual stages.
145+
146+
```scala mdoc
141147
val (state, result) = program.run(1).value
142148
```
143149

144-
=== Exercise: Post-Order Calculator
145150

151+
#exercise([Post-Order Calculator])
146152

147153
The `State` monad allows us to implement
148154
simple interpreters for complex expressions,

0 commit comments

Comments
 (0)