I the spirit of https://xkcd.com/221/, here's a “solution” without any IO:
rng :: Int -> Int
rng upper
| upper<=4 = upper
| otherwise = 4
So that gives you an “RFC 1149.5 compliant random number”. It's always four, unless that is outside the range.
What's the problem with this? Well, clearly it gives always the same number – and so it must be, because all Haskell functions must be functions, i.e. referentially transparent. OTOH, a random number generator is supposed to give different number each time you call it... it is thus not a function, and most other programming languages merely pretend it is a function with side-effect – because they have no proper means to express what side-effects are. Well, Haskell does have a proper means of expressing that, and it is the IO monad: you can have computations that depend on a side effect, but clearly these computations if you run them will then have that side-effect themselves.
In that light, the signature Int -> IO Int
does make sense for the function. (This is a function, but the result is an IO action and only executing that action gives you an Int
.)
What's ugly about that is that IO Int
could literally do anything in IO
– it could, for instance, launch some missiles and give you back the number of casualities. More realistically, it could easily modify some file in you home directory. Whereas what you want is actually just a teeny tiny harmless side-effect, just enough to produce a new random number the next time. Usually, random number generators are anyways not really random but PRNGs, which keep a constant-size state variable that is updated in a random-looking way each time you pull a value. The next time, this state will be different and thus you get a different value, as desired. This state variable could be held in an IO
-mutable location
import Data.IORef
type RandStV = Int
type RandSt = IORef RandStV
rng' :: RandSt -> Int -> IO Int
rng' rSt upper = do
x <- readIORef rSt
let x' = ((x * 1103515245) + 12345) `mod` 0x7fffffff -- https://sourceware.org/git/?p=glibc.git;a=blob;f=stdlib/random_r.c;hb=glibc-2.26#l362
writeIORef rSt x'
return $ x `mod` upper
...or, you could just explicitly pass the updated state along with the result
rng'' :: Int -> RandStV -> (RandStV, Int)
rng'' upper x =
let x' = ((x * 1103515245) + 12345) `mod` 0x7fffffff
in (x', x `mod` upper)
...or it could be passed around in a dedicated state monad, which is just another way of writing the passing-on of an updated variable:
type RandStM = State RandStV
rng''' :: Int -> RandStM Int
rng''' upper = do
x <- get
let x' = ((x * 1103515245) + 12345) `mod` 0x7fffffff
put x'
return $ x `mod` upper
See the random-fu package for useful helpers on such a random monad.
One mathematical way to interpret rng'''
is to say it is a function that takes an upper bound as the argument and gives you back a distribution of numbers. The distribution is always the same, but it “contains” many numbers together with the probability of them occuring. Actually generating an integer means you're sampling from the distribution.