September 18, 2026

Haskell for Hobos -- Random numbers

A note up front. For whatever reason, most distributions of GHC don't include the random number modules, and you have to go to some extra trouble to get your hands on them. See my notes at the end of this page for that.

You wouldn't think that getting random numbers would be a big deal. Just supply a "random" function and then call it.

x = random
The problem is purity. Haskells idea of functions and purity say that any function gives the same result every time given the same inputs. Getting the same number time after time hardly fits anyones idea of randomness. Side effects have meaning in both directions, so there can be no hidden state.

Of course this is a non-issue in an imperative language, but we have to jump through some hoops with Haskell. This is the kind of thing that makes you wonder if functional languages really function all that well. Perhaps some functional languages just make an exception in cases like this -- which leads to a slippery slope -- so Haskell remains adamantly pure and views solving this as a challenge.

Given that fiddling with Random numbers in Haskell is non-trivial, you won't be surprised to learn that there are plenty of online articles. Check the dates though and be warned that there have been changes to the Haskell random number libraries.

The method is to pass state around and set up a generator with an interface like this:

import System.Random

gen = mkStdGen 999
(a, gen2) = random gen :: (Int, StdGen)

main = putStrLn $ show (a, gen2)
Now "random" returns a tuple. The first item is the random number we want. The second item is a new generator, which we can in turn pass to random to get a second random number.

Note that we give a type suffix to indicate the return type, and in particular the sort of random number we would like. We could ask for Float, Integer, or even Bool instead of Int

It should go without saying that this is less than handy if we want a long series of random numbers (or even an infinite number, as we might). We end up having to manage a continual flow of generators, and we can only hope that Haskell garbage collection will be following along to clean up the mess.

Managing a "flow of state" like this is what the State monad was set up to do -- which faces you with figuring it out and cutting that Gordian knot.

You can use recursion to generate lists of random numbers, or just use another function in System.Random that gives you an infinite list:

vals = take 10 $ randoms gen :: [Int]
main = putStrLn $ show vals
This yields:
[8908665983106115265,-5783623306742746687,8188891392167746973,-4419888399603567643,-6731314650093518225,
2383174493572429392,-2259669715982041348,-5930822080450222403,7601007026156292121,3194718743181011224]

LYAH example with State

This is on page 320. This code won't work verbatim with modern Haskell. The System.Random module has been reworked. The basic advice is to replace calls to random with calls to uniform and to add type signatures. I can't make it work.

Another hot tip is to check the online "community" edition of the book. An effort is being made to keep it up to date with modern Haskell compilers.

Click on the cloud that says "read it online". Sad to say, they have not made it to page 320 and the example I am struggling with. I get this error:
• No instance for ‘RandomGen stdGen’
        arising from a use of ‘randomS’
		a <- randomS
It isn't worth the time I am spending on it. A language with unstable API will turn away many people eager to work with it.

Control.Monad.Random

I tripped over this while trying to figure out how to get the System.Random package installed on Fedora.
grep Random dnf.list | grep ghc
ghc-MonadRandom.x86_64
ghc-MonadRandom-devel.x86_64
ghc-MonadRandom-doc.noarch
ghc-MonadRandom-prof.x86_64
Be sure to install it via:
dnf install ghc-MonadRandom-devel

For the record, let it be noted that there is also System.Random.Stateful, which I can now import since I have the System.Random package business sorted out.

After installing ghc-MonadRandom-devel, I can do "import Control.Monad.Random" and experiment with it.

System.Random.Stateful

I found this nice bit of example code online in this Reddit discussion. You will notice the discussion talking about Monad transformers, which are yet something else to learn about. The rework of the Haskell random packages apparently involved refactoring to use these for some benefit that I don't yet understand.

Notice also that it is using System.Random.Stateful

import Control.Monad.Random (MonadRandom (getRandomRs), evalRand, mkStdGen)

randomString :: MonadRandom m => String -> Int -> m String
randomString charset len =
    take len . map (charset !!) <$> getRandomRs (0, length charset - 1)

demoString :: String
demoString = evalRand (randomString ['!'..'~'] 16) (mkStdGen 42)

main :: IO ()
main = putStrLn demoString
This yields: 0qs28,S\&/J}|c*l

The heart of this is the use of <$> to lift a function into the monad. This is simpler to see in the following example:

To just get a list of integers (as I sometimes want), I fiddled the code to this:

import Control.Monad.Random (MonadRandom (getRandomRs), evalRand, mkStdGen)

import System.Random.Stateful

randomList :: MonadRandom m => Int -> m [Int]
randomList len =
    take len <$> getRandomRs (0, 999)

demoList :: [Int]
demoList = evalRand (randomList 16) (mkStdGen 42)

main :: IO ()
main = putStrLn $ show demoList
Which yields: [95,783,208,466,913,535,651,95,500,306,699,901,398,809,760,606]

Here the "lifting" of the "take" function is done by <$>, which is the infix form of "fmap". The mapping is not to the elements of the list, but to the list itself which is contained in the MonadRandom monad.

Here "evalRand" works the same as the "runState" function with respect to the State monad, which you can go study.

Getting System.Random on my Fedora install of GHC

I am (as of 9-2026) running ghc 9.10.3 as this indicates:
Package "ghc-9.10.3-155.fc44.x86_64" is already installed.

When I start trying to play with example code in LYAH, I find that I don't have System.Random. I also find, when I begin searching, that is is somewhat common for this not to be included in a normal ghc install (which is stupid). The same is true for Control.Monad.Random -- but I finally figured out how to get them both. The way is:

dnf install ghc-random-devel
dnf install ghc-MonadRandom-devel
Installing without the -devel suffix just gives you shared libraries. This is pretty standard Fedora package management, assuming that you just want to run compiled binaries, not develop code.
However, this is probably true one time out of 1000 for people using Haskell!

Some online advice suggested using "cabal". There is no reason to do this on Fedora given that packages are available.
I played with it as recommended (but as user root), which was in no way useful. It probably created some inconsistent setup for user root, but I never run Haskell as root, so whatever mess got made can be ignored.

su
dnf install cabal-install
cabal update
cabal install --lib random
The last "cabal install" command fetched me this big warning:
Warning: The libraries were installed by creating a global GHC environment
file at:
/root/.ghc/x86_64-linux-9.10.3/environments/default

The presence of such an environment file is likely to confuse or break other
tools because it changes GHC's behaviour: it changes the default package set
in ghc and ghci from its normal value (which is "all boot libraries"). GHC
environment files are little-used and often not tested for.

Furthermore, management of these environment files is still more difficult
than it could be; see e.g. https://github.com/haskell/cabal/issues/6481 .

Double-check that creating a global GHC environment file is really what you
wanted! You can limit the effects of the environment file by creating it in a
specific directory using the --package-env flag. For example, use:

cabal install --lib  --package-env .
%nav
Have any comments? Questions? Drop me a line!

Tom's software pages / tom@mmto.org