Skip to content

Commit 1078c8e

Browse files
authored
Merge pull request #133 from rhys-newbury/master
Secret Solutions to Workshop
2 parents 124489a + 390e81a commit 1078c8e

File tree

3 files changed

+209
-0
lines changed

3 files changed

+209
-0
lines changed

_chapters/randmonad.md

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
---
2+
layout: chapter
3+
title: "Rand Monad"
4+
---
5+
6+
# Rand Monad
7+
8+
```haskell
9+
newtype Rand a = Rand { next :: Seed -> (Seed, a) }
10+
```
11+
12+
`Rand` is a newtype wrapper around a function `Seed -> (Seed, a)`.
13+
It represents a computation that, given a starting Seed, produces:
14+
15+
1. A new updated `Seed`.
16+
2. A value of type `a`.
17+
18+
![Rand Monad](/assets/images/chapterImages/randmonad/randMonad.png)
19+
20+
## Functor
21+
22+
Definition of `Functor`. The `Functor` instance for `Rand` allows you to map a function over the result of a random computation.
23+
24+
```haskell
25+
instance Functor Rand where
26+
fmap :: (a -> b) -> Rand a -> Rand b
27+
fmap f (Rand g) = Rand h
28+
where
29+
-- The function inside rand
30+
-- Apply f to the `value` a
31+
h seed = (newSeed, f a)
32+
where
33+
(newSeed, a) = g seed
34+
```
35+
36+
fmap constructs a new Rand value, Rand h, by:
37+
38+
1. Taking a function `f` and a random computation `Rand g`.
39+
2. Defining a new function `h` that, given an initial Seed, runs `g` to get `(newSeed, a)`.
40+
3. Returning `(newSeed, f a)`, where `f a` is the transformed value.
41+
42+
After applying fmap f, we have a new random computation that takes the same Seed as input and produces a transformed value (f a), while maintaining the same mechanics of randomness (i.e., correctly passing and updating the Seed state).
43+
44+
We can also be a bit more succinct, by making use of `fmap` instances
45+
46+
```haskell
47+
fmap :: (a -> b) -> Rand a -> Rand b
48+
fmap f r = Rand $ (f <$>)<$> next r
49+
```
50+
51+
## Applicative
52+
53+
```haskell
54+
pure :: a -> Rand a
55+
pure x = Rand (,x) -- Return the input seed and the value
56+
57+
58+
(<*>) :: Rand (a -> b) -> Rand a -> Rand b
59+
left <*> right = Rand h
60+
where
61+
h s = (s'', f v) -- Need to return a function of type (Seed -> (Seed, Value))
62+
where
63+
(s', f) = next left s -- Get the next seed and function from the left Rand
64+
(s'', v) = next right s' -- Get the next seed and value from the right Rand
65+
```
66+
67+
`<*>` constructs a new Rand value Rand h by:
68+
69+
1. Extracting a function `f` from the left computation using the initial seed.
70+
2. Using the new seed to extract a value `v` from the right computation.
71+
3. Returning a new seed and the result of applying `f` to `v`.
72+
4. This allows us to apply a random function to a random value in a sequence while maintaining proper state management of the Seed.
73+
74+
## Monad
75+
76+
```haskell
77+
instance Monad Rand where
78+
(>>=) :: Rand a -> (a -> Rand b) -> Rand b
79+
r >>= f = Rand $ \s ->
80+
let (s1, val) = next r s
81+
in next (f val) s1
82+
```
83+
84+
`r >>= f` creates a new Rand computation by:
85+
86+
1. Running the first computation `r` with the initial seed `s`.
87+
2. Extracting the value `val` and the new seed `s1`.
88+
3. Using `val` to determine the next random computation `f val`.
89+
4. Running `f val` with the updated seed `s1` to produce the final result.
90+
91+
![Bind](/assets/images/chapterImages/randmonad/bind.png)
92+
93+
## `Get` and `Put`
94+
95+
`put` is used to set the internal state (the `Seed`) of the `Rand` monad. There is no value yet, hence we use the unit (`()`)
96+
97+
`put` allows us to **modify** the internal state (Seed) of a random computation.
98+
99+
```haskell
100+
put :: Seed -> Rand ()
101+
put newSeed = Rand $ \_ -> (newSeed, ())
102+
```
103+
104+
`get` is used to retrieve the current state (the `Seed`) from the `Rand` monad.
105+
It **does not modify** the state but instead returns the current seed as the result. This is achieved by putting the current seed in to the value part of the tuple.
106+
107+
Since, when we apply transformation on the tuple, we apply the transformation according to the value!
108+
109+
```haskell
110+
get :: Rand Seed
111+
get = Rand $ \s -> (s, s)
112+
```
113+
114+
Using `get` and the monad instance, we can make a function to increase the seed by one.
115+
116+
```haskell
117+
incrementSeed' :: Rand Seed
118+
incrementSeed' = get >>= \s -> pure (s + 1)
119+
```
120+
121+
```haskell
122+
incrementSeed :: Rand Seed
123+
incrementSeed = do
124+
seed <- get -- This gets the current seed
125+
return (seed + 1) -- Increment the seed and put it in the 'state', we can do anything with the seed!
126+
```
127+
128+
```bash
129+
>>> next incrementSeed' 123
130+
(123, 124)
131+
```
132+
133+
## Modifying a seed
134+
135+
We want to modify the seed, assuming there is no value. This will simply apply a function `f` to the current seed.
136+
137+
```haskell
138+
modify :: (Seed -> Seed) -> Rand ()
139+
modify f = Rand $ \s -> (f s, ())
140+
```
141+
142+
We can also write this using our `get` and `put`
143+
144+
```haskell
145+
modify :: (Seed -> Seed) -> Rand ()
146+
modify f = get >>= \s -> put (f s)
147+
```
148+
149+
This function:
150+
151+
1. First retrieves the current seed (`get`).
152+
2. Applies the function `f` to modify the seed.
153+
3. Updates the internal state with `put`.
154+
155+
This computation returns () as its result, indicating that its purpose is to update the state, not to produce a value.
156+
157+
We can now write our `incrementSeed` in terms of modify
158+
159+
```haskell
160+
incrementSeed :: Rand Seed
161+
incrementSeed = do
162+
modify (+1) -- Use `modify` to increment the seed by 1
163+
get -- Return the updated seed
164+
```
165+
166+
## Rolling A Dice
167+
168+
```haskell
169+
nextRand :: Seed -> Seed
170+
nextRand prevSeed = (a*prevSeed + c) `mod` m
171+
where -- Parameters for linear congruential RNG.
172+
a = 1664525
173+
c = 1013904223
174+
m = 2^32
175+
176+
-- | Generate a number between `l` and `u`.
177+
genRand :: Int -> Int -> Seed -> Int
178+
genRand l u seed = seed `mod` (u-l+1) + l
179+
```
180+
181+
Using the above two functions and our knowledge, we can make a function which rolls a dice. This will require 3 parts.
182+
183+
1. Using `nextRand` to update the current seed
184+
2. Get the seed from the state
185+
3. Call `genRand` to get the integer.
186+
187+
```haskell
188+
rollDie :: Rand Int
189+
rollDie = do
190+
modify nextRand -- update the current seed
191+
s <- get -- get retrieves the updated seed value s from the Rand monad's state.
192+
pure (genRand 1 6 s) -- computes a random number and puts back in the context
193+
```
194+
195+
We can also write this using bind notation, where we `modify nextRand` to update the seed. We then use `>>` to ignore the result (i.e., the `()`). We use get to put the seed as the value, which is then binded on to `s` and used to generate a random number. We then use pure to update the value, the seed updating is handled by our bind!
196+
197+
```haskell
198+
rollDie :: Rand Int
199+
rollDie = modify nextRand >> get >>= \s -> pure (genRand 1 6 s)
200+
```
201+
202+
Finally, how we can use this?
203+
204+
```bash
205+
>>> next rollDie 123
206+
(1218640798,5)
207+
```
208+
209+
`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
187 KB
Loading
Loading

0 commit comments

Comments
 (0)