I'm trying to get a random number in Haskell. (Which I'm currently learning and haven't got on to Monads or IO, etc) the problem is the functions in System.Random all return an IO Int, which I then can't use in the rest of my code which uses Int and Float.
The objective here is to choose a pair from a list where the first of the pair is a float representing a probability. So my plan was to use a random number to choose a pair based on its probability.
This is a common barrier for new Haskell programmers. You want to escape IO, and it takes a while to figure out the best way to do so. The Learn You A Haskell tutorial has a good explanation of one way to generate random numbers using the state monad, but it still has to be seeded using getStdGen
or newStdGen
, which are in IO.
For simple cases you can just do something like
myPureFunction :: Float -> Float
myPureFunction x = 2 * x
main :: IO ()
main = do
-- num :: Float
num <- randomIO :: IO Float
-- This "extracts" the float from IO Float and binds it to the name num
print $ myPureFunction num
So you see, you can get your random number in main
, then pass that value to a pure function that does the processing.
You may be asking yourself why there's all this work to generate random numbers in Haskell. There are numerous good reasons, most of which have to do with the type system. Since generating random numbers requires modifying the state of the StdGen in the operating system, it has to live inside IO
, otherwise you could have a pure function that gives you different results each time.
Imagine this contrived scenario:
myConstant :: Int
myConstant = unsafePerformIO randomIO
blowUpTheWorld :: IO ()
blowUpTheWorld = error "Firing all the nukes"
main :: IO ()
main = do
if even myConstant
then print "myConstant is even"
else blowUpTheWorld
If you ran this a few times, chances are that you would end up "firing all the nukes". Obviously, this is bad. myConstant
should be, well, constant, but each time you run the program you'd get a different value. Haskell wants to guarantee that a pure function will always return the same value given the same inputs.
It may be annoying right now, but it's a powerful tool in the functional programmer's kit.
There are good answers here, but I felt a more complete answer would show very simply how to get and use random numbers in Haskell, in a way that would make sense to imperative programmers.
First, you need a random seed:
import System.Random
newRand = randomIO :: IO Int
Because newRand
is of type IO Int
and not Int
, it cannot be used as a function parameter. (This preserves Haskell functions as pure functions that will always return the same result on the same input.)
We can, however, simply enter newRand
in GHCI and get a unique random seed each time. This is possible only because newRand
is of type IO
and is not a standard (immutable) variable or function.
*Main> newRand
-958036805781772734
We can then copy and paste this seed value into a function that creates a list of random numbers for us. If we define the following function:
randomList :: Int -> [Double]
randomList seed = randoms (mkStdGen seed) :: [Double]
And paste in the given seed when the function is run in GHCI:
*Main> take 10 randomList (-958036805781772734)
[0.3173710114340238,0.9038063995872138,0.26811089937893495,0.2091390866782773,0.6351036926797997,0.7343088946561198,0.7964520135357062,0.7536521528870826,0.4695927477527754,0.2940288797844678]
Notice how we get the familiar values from 0 to 1 (exclusive). Instead of generating a new random number each iteration like we would in an imperative language, we generate a list of random numbers ahead of time and use the head of the tail of the list on each successive recursion. An example:
pythagCheck :: [Double] -> [Double] -> [Int]
pythagCheck (x:xs) (y:ys)
| (a^2) + (b^2) == (c^2) = [a, b, c]
| otherwise = pythagCheck xs ys
where aplusb = ceiling (x * 666)
a = ceiling (y * (fromIntegral (aplusb - 1)))
b = aplusb - a
c = 1000 - a - b
Creating two lists ahead of time and feeding them in as parameters allows us to search for the (one and only!) Pythagorean triple where a + b + c = 1000. You would, of course, want to use a different random seed for each list:
*Main> newRand
3869386208656114178
*Main> newRand
-5497233178519884041
*Main> list1 = randomList 3869386208656114178
*Main> list2 = randomList (-5497233178519884041)
*Main> pythagCheck list1 list2
[200,375,425]
As already said, random numbers can't really be pure values1.
However, this doesn't really need to bother you. Just look at it the other way around: other languages simply don't have such a thing as pure values, it's always states with real-world interference you're dealing with. Haskell can do that as well, in the IO
monad. You don't need to know how exactly that works, just imitate what it would look like in a procedural language (there are a few pitfalls here, though).
First of all you need some algorithm, that doesn't have anything to do with the language whatsoever. The obvious way is to accumulate the probabilities across the list, and use the resulting step-function as a map from [0, 1[ to your desired values.
probsListLookup :: [(Double, a)] -> Double -> a
probsListLookup pAssoc = look acc'dList
where acc'dList = scanl1 (\(pa,_) (pn,x) -> (pa+pn,x)) pAssoc
look ((pa, x) : pas) rval
| rval < pa = look pas rval
| otherwise = x
Note that this neither handles invalid inputs well (probabilities not summing to 1 etc.) nor is it efficient, scrambling O (n) through acc'dList
for each requested value2. More important though, note that it's a pure function! It's generally a good idea to use pure functions as much as possible, and only go into IO
when it's absolutely necessary. Like now: we need to obtain a single Double
value between 0 and 1. Easy!
main = do
lookupVal <- randomRIO (0, 1)
print $ probsListLookup [(0.1, 1), (0.2, 2), (0.3, 4), (0.4, 5)] lookupVal
1At least not of a basic type like Int
; you could actually do "pure computations" on whole probabilty distributions though. Doing that explicitly is very cumbersome, but Haskell allows you to use specific monads (or in fact comonads) to make that just as easy as it is in Haskell IO (or in any impure language) but without the dangers of input/output.
2You could improve that e.g. with Data.Map
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With