Generate a random integer in a range in Haskell wi

2020-02-08 07:38发布

问题:

How can I generate a random number in Haskell from a range (a, b) without using any seed?

The function should return an Int and not an IO Int. I have a function X that takes and Int and other arguments and outputs something which is not an IO.

If this is not possible, how can I generate a seed using the Time library and the generate a random number in the range with the mkStdGen ?

Any help would be really appreciated.

回答1:

A function cannot return an Int without IO, unless it is a pure function, i.e. given the same input you will always get the same output. This means that if you want a random number without IO, you will need to take a seed as an argument.

  • If you choose to take a seed, it should be of type StdGen, and you can use randomR to generate a number from it. Use newStdGen to create a new seed (this will have to be done in IO).

    > import System.Random
    > g <- newStdGen
    > randomR (1, 10) g
    (1,1012529354 2147442707)
    

    The result of randomR is a tuple where the first element is the random value, and the second is a new seed to use for generating more values.

  • Otherwise, you can use randomRIO to get a random number directly in the IO monad, with all the StdGen stuff taken care of for you:

    > import System.Random
    > randomRIO (1, 10)
    6
    


回答2:

Without resorting to all kinds of unsafe practices, it is not possible for such a function to have type Int rather than type IO Int or something similar. Functions (or, in this case, constants) of type Int are pure, meaning that every time that you "invoke" the function (retrieve the value of the constant) you are guaranteed to get the same value "returned".

If you want to have a different, randomly chosen value returned at every invocation, you will need to use the IO-monad.

In some occasions, you may want to have a single randomly produced value for the whole program, i.e., one that, from the program's perspective behaves as if it were a pure value. Every time you query the value within the same run of the program, you get the same value back. As the whole program is essentially an IO-action you could then generate that value once and pass it around, but this may feel a bit clumsy. One could argue that, in this situation, it is still safe to associate the value with a top-level constant of type Int and use unsafePerformIO to construct that constant:

import System.IO.Unsafe  -- be careful!                                         
import System.Random

-- a randomly chosen, program-scoped constant from the range [0 .. 9]            
c :: Int
c = unsafePerformIO (getStdRandom (randomR (0, 9)))


回答3:

fmap yourFunctionX $ randomRIO (a, b)

or

fmap (\x -> yourFunctionX aParam x anotherParam) $ randomRIO (a, b)

The result will then be of type IO whateverYourFunctionXReturns.

If you import Control.Applicative, you can say

yourFunctionX <$> randomRIO (a, b)

or

(\x -> yourFunctionX aParam x anotherParam) <$> randomRIO (a, b)

which you may find clearer



回答4:

Note that you can get an infinite list of random values using the IO monad and use that [Int] in non-IO functions. This way you don't have to carry the seed along with you, but still need to carry the list of course. Fortunately, there are plenty of list processing functions to simplify such threading, and you can still use the State monad in complicated cases.

Also note that you can easily convert an IO Int to an Int. If foo produces an IO Int, and bar takes an Int as its only parameter and returns a non-IO value, the following will do:

foo >>= return . bar

Or using do notation:

do 
    a <- foo
    return $ bar a

Or using fmap (monads are functors, and <$> is an infix version of fmap):

bar <$> foo


回答5:

I used SipHash for that purpose

import Data.ByteArray.Hash
import Data.ByteString (pack, cons)
import Data.Word (Word8, Word64)

random :: Word64 -> Word64 -> [Word8] -> Double
random a b cs = (subtract 1) . (/(2**63)) . read . drop 8 . show $ sipHash (SipKey a b) (pack cs)

the read and drop 8 and show serve the purpose to drop a newtype that doesn’t (or didn’t when I implemented this) support any casting

now you want an Int in a range. Integer is easier though:

random :: Word64 -> Word64 -> [Word8] -> (Integer, Integer) -> Integer
random a b cs (low,high) = let
    span = high-low
    rand = read . drop 8 . show $ sipHash (SipKey a b) (pack cs)
    in (rand `mod` span) + low

of course, you’ll still get the same number for the same arguments every time, so you’ll need to vary them ie you still pass around arguments, just not returned values too. Whether that’s more convenient than a monad depends (for my purpose it was)

this is how I made sure arguments (specifically the [Word8] argument) would always be different:

foo bytes = doSomethingRandom bytes
bar bytes = map (\i -> foo (i:bytes)) [1..n]
baz bytes = doSomething (foo (0:bytes)) (bar (1:bytes))