You wouldn't think that getting random numbers would be a big deal. Just supply a "random" function and then call it.
x = randomThe 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 valsThis yields:
[8908665983106115265,-5783623306742746687,8188891392167746973,-4419888399603567643,-6731314650093518225, 2383174493572429392,-2259669715982041348,-5930822080450222403,7601007026156292121,3194718743181011224]
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.
grep Random dnf.list | grep ghc ghc-MonadRandom.x86_64 ghc-MonadRandom-devel.x86_64 ghc-MonadRandom-doc.noarch ghc-MonadRandom-prof.x86_64Be 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.
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.
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-develInstalling 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.
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 randomThe 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%nav--package-env .
Tom's software pages / tom@mmto.org