1

I have code that looks like:

import qualified Data.Vector as V
import System.Random
import Control.Monad.State.Lazy
nextWeightedRandom :: State (StdGen, V.Vector Int) Int
nextWeightedRandom = do
  (g, fs) <- get
  let (i, g') = randomR (0, V.length fs - 1) g
  put (g', fs)
  return (fs V.! i)

weightedRandomList :: (StdGen, V.Vector Int) -> [Int]
weightedRandomList = evalState $ mapM (\_ -> nextWeightedRandom) [1..]

QuickCheck confirms that the values coming out of weightedRandomList are different and have roughly the distribution that I was hoping for (the Vector Int looks like [5, 5, 5, 10], so that nextWeightedRandom spits out 5 with probability 3/4 and 10 with probability 1/4).

I was able to get it working by guessing my way though the types, but I'm very confused --- isn't evalState getting run over a bunch of different copies of the same generator? Why doesn't the list produced look like [5, 5, 5, 5,...]?

Patrick Collins
  • 10,306
  • 5
  • 30
  • 69
  • 1
    You might like [`randomRs`](https://hackage.haskell.org/package/random-1.1/docs/System-Random.html#v:randomRs). – Daniel Wagner Apr 30 '15 at 05:57
  • 1
    @DanielWagner That won't weight it according to a given distribution. Also, my main confusion with this question is with the workings of the `State` monad, rather than finding a better way to generate random numbers. – Patrick Collins Apr 30 '15 at 05:59
  • I understand what your confusion is, and am writing an answer about it. Comments are for commenting, not answering. As for the given distribution -- well, neither does `randomR`. You can apply the same transformation on the infinite list that `randomRs` gives you that you can to the elements that `randomR` gives you. e.g. `weightedRandomList (g, fs) = map (fs V.!) (randomRs (0, V.length fs - 1) g)`. – Daniel Wagner Apr 30 '15 at 06:02

1 Answers1

4

Since you call put in nextWeightedRandom, the state gets updated on each iteration. So each time you call randomR ... g, it is using the generator output by the previous iteration.

In fact, the question itself, "isn't evalState getting run over a bunch different copies of the same generator" is quite an interesting one, since evalState isn't handed many copies of anything! It is given a single generator, and does all its work starting from there.

Daniel Wagner
  • 145,880
  • 9
  • 220
  • 380
  • I'm having trouble wrapping my head around this -- I understand `mapM` to be returning something that looks like `[nextWeightedRandom, nextWeightedRandom, nextWeightedRandom,...]`, so I expect each evaluation to return the same result -- am I wrong about that? – Patrick Collins Apr 30 '15 at 06:05
  • 1
    @PatrickCollins `mapM` does more than that, it chains those monadic actions using `>>=` which is defined by the `State` monad. Concretely, you can regard each `nextWeigthedRandom` as a function taking the old random seed, and returning a new integer as well a a new random seed. The operator `>>=` simply concatenates the seeds. In this way, each call to `nextWeightedRandom` has a different seed argument, and can return a different integer. – chi Apr 30 '15 at 12:47
  • @chi Ah, okay. So `(:)` is `>>=` in the list monad? – Patrick Collins Apr 30 '15 at 22:26
  • @PatrickCollins No, it's `mapM` that replaces `:` with `>>=`, roughly. The list monad is not involved here: this `mapM` works in the `State` monad. – chi Apr 30 '15 at 22:47
  • @chi I understand that --- maybe I was unclear. I meant that when I call `mapM` inside the list monad then I get back a bunch of values joined together by the `(:)` operator. When I call it inside the state monad, I get back a bunch of values joined together by `>>=` -- does that mean that `(:)` is an implementation of `>>=` in the list monad? – Patrick Collins Apr 30 '15 at 23:19
  • 1
    @PatrickCollins No, `:` is not the implementation of `>>=` for lists. Additionally, "a bunch of values joined together by the `:` operator" is a description of *any* list at all, no matter how it was computed; that should make you nervous about attributing that property to the use of `mapM`. – Daniel Wagner Apr 30 '15 at 23:53