Skip to content

Commit 8d3a47a

Browse files
committed
State monad
1 parent 6ff0015 commit 8d3a47a

File tree

1 file changed

+61
-7
lines changed

1 file changed

+61
-7
lines changed

_chapters/randmonad.md

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,11 @@ For example:
3535
-- (4,1868869221)
3636
-- >>> rollDie1 1868869221
3737
-- (1,166005888)
38-
rollDie1 :: Seed -> (Int, Seed)
38+
rollDie1 :: Seed -> (Seed, Int)
3939
rollDie1 s =
4040
let s' = nextRand s
4141
n = genRand 1 6 s'
42-
in (n, s')
42+
in (s', n)
4343
```
4444

4545
And if we want a sequence of dice rolls:
@@ -48,12 +48,12 @@ And if we want a sequence of dice rolls:
4848
-- | Roll a six-sided die `n` times.
4949
-- >>> diceRolls1 3 123
5050
-- ([5,4,1],166005888)
51-
diceRolls1 :: Int -> Seed -> ([Int], Seed)
51+
diceRolls1 :: Int -> Seed -> (Seed, [Int])
5252
diceRolls1 0 s = ([], s)
5353
diceRolls1 n s =
54-
let (r, s') = rollDie1 s
55-
(rolls, s'') = diceRolls1 (n-1) s'
56-
in (r:rolls, s'')
54+
let (s', r) = rollDie1 s
55+
(s'', rolls) = diceRolls1 (n-1) s'
56+
in (s'', r:rolls)
5757
```
5858

5959
But keeping track of the various seeds (`s`,`s'`,`s''`) is tedious and error prone. Let's invent a monad which manages the seed for us. The seed will be threaded through all of our functions implicitly in the monadic return type.
@@ -261,4 +261,58 @@ Finally, how we can use this?
261261
(1218640798,5)
262262
```
263263
264-
`next` is used on `rollDie` to get the function of type `Seed -> (Seed, a)`. We then call this function with a seed value of `123`, to get a new seed and a dice roll
264+
`next` is used on `rollDie` to get the function of type `Seed -> (Seed, a)`. We then call this function with a seed value of `123`, to get a new seed and a dice roll.
265+
266+
Now, here's how we get a list of dice rolls using a direct adaptation of our previous code, but trusting the `Rand` monad to thread the `Seed` through for us. No more messy wiring up of parameters and inventing arbitrary variable names.
267+
268+
```haskell
269+
-- | Roll a six-sided die `n` times.
270+
-- >>> runState (diceRolls 3) 123
271+
-- ([5,4,1],166005888)
272+
diceRolls :: Int -> Rand [Int]
273+
diceRolls 0 = pure []
274+
diceRolls n = do
275+
r <- rollDie
276+
rest <- diceRolls (n-1)
277+
pure (r:rest)
278+
```
279+
280+
## State Monad
281+
282+
Of course, Haskell libraries are extensive, and if you can think of a monad that's useful, there's probably a version of it already in the libraries somewhere.
283+
284+
Actually, we'll use two libraries. We'll replace our `Seed` type with `StdGen` and `nextRand` with `randomR`, both from `System.Random`. We'll replace our custom `Rand` monad with `Control.Monad.State`.
285+
286+
In `diceRolls`, we'll also replace the recursive list construction, with `replicateM`, which just runs a function with a monadic effect `n` times, placing the results in a list.
287+
288+
```haskell
289+
module StateDie
290+
where
291+
292+
import System.Random
293+
import Control.Monad.State
294+
295+
-- in System.Random seeds have type StdGen, we'll make the starting seed:
296+
seed :: StdGen
297+
seed = mkStdGen 123
298+
299+
-- remake the Rand monad, but using the State monad to store the seed
300+
type Rand a = State StdGen a
301+
302+
-- remake genRand using the randomR function from System.Random
303+
-- instead of our custom nextRand function
304+
genRand :: Int -> Int -> Rand Int
305+
genRand lower upper = state (randomR (lower,upper))
306+
307+
-- | A function that simulates rolling a six-sided dice
308+
-- >>> runState rollDie seed
309+
-- (1,StdGen ...)
310+
rollDie :: Rand Int
311+
rollDie = genRand 1 6
312+
313+
-- | Roll a six-sided die `n` times.
314+
-- >>> runState (diceRolls 3) seed
315+
-- ([1,5,6],StdGen ...)
316+
diceRolls :: Int -> Rand [Int]
317+
diceRolls n = replicateM n rollDie
318+
```

0 commit comments

Comments
 (0)