Learn You a Haskell for Great Good! - Miran Lipovaca [85]
ghci> random (mkStdGen 100)
Ambiguous type variable `a' in the constraint: `Random a' arising from a use of `random' at Probable fix: add a type signature that fixes these type variable(s) What’s this? Ah, right, the random function can return a value of any type that’s part of the Random type class, so we need to inform Haskell which type we want. Also let’s not forget that it returns a random value and a random generator in a pair. ghci> random (mkStdGen 100) :: (Int, StdGen) (-1352021624,651872571 1655838864) Finally, a number that looks kind of random! The first component of the tuple is our number, and the second component is a textual representation of our new random generator. What happens if we call random with the same random generator again? ghci> random (mkStdGen 100) :: (Int, StdGen) (-1352021624,651872571 1655838864) Of course, we get the same result for the same parameters. So let’s try giving it a different random generator as a parameter: ghci> random (mkStdGen 949494) :: (Int, StdGen) (539963926,466647808 1655838864) Great, a different number! We can use the type annotation to get different types back from that function. ghci> random (mkStdGen 949488) :: (Float, StdGen) (0.8938442,1597344447 1655838864) ghci> random (mkStdGen 949488) :: (Bool, StdGen) (False,1485632275 40692) ghci> random (mkStdGen 949488) :: (Integer, StdGen) (1691547873,1597344447 1655838864) Tossing a Coin We’ll represent a coin with a simple Bool: True is tails, and False is heads. threeCoins :: StdGen -> (Bool, Bool, Bool) threeCoins gen = let (firstCoin, newGen) = random gen (secondCoin, newGen') = random newGen (thirdCoin, newGen'') = random newGen' in (firstCoin, secondCoin, thirdCoin) We call random with the generator we got as a parameter to get a coin and a new generator. Then we call it again, only this time with our new generator, to get the second coin. We do the same for the third coin. Had we called it with the same generator every time, all the coins would have had the same value, so we would get only (False, False, False) or (True, True, True) as a result. ghci> threeCoins (mkStdGen 21) (True,True,True) ghci> threeCoins (mkStdGen 22) (True,False,True) ghci> threeCoins (mkStdGen 943) (True,False,True) ghci> threeCoins (mkStdGen 944) (True,True,True) Notice that we didn’t need to call random gen :: (Bool, StdGen). Since we already specified that we want Booleans in the type declaration of the function, Haskell can infer that we want a Boolean value in this case. More Random Functions ghci> take 5 $ randoms (mkStdGen 11) :: [Int] [-1807975507,545074951,-1015194702,-1622477312,-502893664] ghci> take 5 $ randoms (mkStdGen 11) :: [Bool] [True,True,True,True,False] ghci> take 5 $ randoms (mkStdGen 11) :: [Float] [7.904789e-2,0.62691015,0.26363158,0.12223756,0.38291094] Why doesn’t randoms return a new generator as well as a list? We could implement the randoms function very easily like this: randoms' :: (RandomGen g, Random a) => g -> [a] randoms' gen = let (value, newGen) = random gen in value:randoms' newGen This is a recursive definition. We get a random value and a new generator
Let’s make a function that simulates tossing a coin three times. If random didn’t return a new generator along with a random value, we would need to make this function take three random generators as a parameter and return coin tosses for each of them. But if one generator can make a random value of type Int (which can take on a load of different values), it should be able to make three coin tosses (which can have only eight different end results). So this is where random returning a new generator along with a value comes in handy.
What if we want to flip more coins? For that, there’s a function called randoms, which takes a generator and returns an infinite sequence of values based on that generator.