|
| 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 | + |
| 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 | + |
| 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 |
0 commit comments