Jump to content

Haskell/Solutions/Understanding monads

From Wikibooks, open books for an open world

Random Number Generation

[edit | edit source]
Exercises
  1. Roll two dice! With sumTwoDice that is :-) . Use fst to extract the result.
  2. Write a function rollNDice :: Int -> Seed -> ([Int],Seed) that rolls dice n times and returns a list of the n results. Extra: If you know about infinite lists, use unfoldr and take to get the result list (but without seed this time).
  3. Reimplement Seed and rollDie with StdGen and random from System.Random.
  4. Now that you have random numbers, do some statistical experiments with the help of rollNDice. For example, do a sanity check that rollDie is not skewed and returns each number with equal likelihood. How is the sum of pips of a double dice roll distributed? The difference? And triple rolls?

2. Using randomNext , Seed and rollDie as defined in the chapter:

rollNDice :: Int -> Seed -> ([Int], Seed)
rollNDice 0 seed = ([], seed)
rollNDice n seed = let (die,  seed0) = rollDie seed
                       (rest, seed1) = rollNDice (n-1) seed0
                   in ((die : rest) ,  
                       seed1)

The function gains the first die and seed and then recursively calls itself to roll N number of die which, once RollNDice reaches the base case, in turn builds up a list of die. The function then returns the list of die and the final seed that was passed to the base case, rollNDice 0 seed = ([], seed).

3. We use mkStdGen to get the computer's random number generator and we give it an arbitrary number, 232. Firstly with import System.Random at the top of your file:

type Seed = StdGen
rollDie1 :: Seed -> (Int, Seed)
rollDie1 seed = randomR (1,6) seed

In Prelude:

Main> rollDie1 (mkStdGen 232)
(3,1017593109 40692)

Threading the State with bind

[edit | edit source]
Exercises
  1. Implement rollNDice :: Int -> Random [Int] from the previous subsection with >>= and return.

1. You have seen everything below the definition of (>>==) before. So everything up to that point is just there to set everything up. I've named >>= >>== and return return1 because this chapter uses bind and return to deal with random numbers specifically; whereas the real bind and return are more generalised and thus do not work for reasons that are somewhere over -----------------> there. At least it works; what more do you want from me? Blood?

With import System.Random at the top of your file:

type Seed = StdGen
type Random1 a = Seed -> (a, Seed)

rollDie :: Random1 Int 
rollDie seed = randomR (1,6) seed

(>>==) :: Random1 a -> (a -> Random1 b) -> Random1 b
(>>==) m g = \seed0 -> 
  let (result1, seed1) = m seed0
      (result2, seed2) = (g result1) seed1
  in (result2, seed2)

return1 :: a -> Random1 a
return1 x = \seed0 -> (x, seed0)

rollNDice1 :: Int -> Random1 [Int]
rollNDice1 0 = return1 []
rollNDice1 n = rollDie >>== (\d1 -> rollNDice1 (n-1) >>== (\rest -> return1 (d1 : rest)))

In prelude:

 
*Main> rollNDice1 20 (mkStdGen 231)
([4,3,1,1,2,3,5,4,2,5,6,4,6,2,4,5,6,5,1,5],1927676552 238604751)

In case you're wondering: You pass (mkStdGen 232111) to rollNDice1 because rollNDice 20 on its own only creates a Random1 [Int]; you need to pass the newly formed Random1 [Int] a seed, in the form of (mkStdGen 242), to get it shooting out random numbers.

Input/Output needs bind

[edit | edit source]
Exercises
  1. Write a function putString :: String -> IO () that outputs a sequence of characters with the help of putChar.
  2. The program loop :: IO () loop = return () >> loop loops forever whereas loopX :: IO () loopX = putChar 'X' >> loopX prints an infinite sequence XXXXXX... of X-s. Clearly, a user can easily distinguish them by looking on the screen. However, show that the model IO a = World -> (a, World) gives the same denotation ⊥ for both. This means that we have to abandon this model as possible semantics for IO a.

1.

putString :: String -> IO ()
putString [] = putChar '\n'
putString (s:xs) = putChar s >> putString xs

The State Monad

[edit | edit source]
Exercises
Correct the definition for (>>=) to take the State constructor into consideration. You'll need to use pattern matching to remove the State constructor.
(State a) >>= f = State $ \s -> let (a',s') = a s
                                    (State b) = f a'
                                in b s'
Exercises
  1. Slightly rewrite your definition of (>>=) to use runState instead of pattern-matching on State
a >>= f = State $ \s -> let (a',s') = runState a s
                        in runState (f a') s'