[A little more progress on the state monad. Bryan O'Sullivan **20080111084507] { hunk ./en/ch14-monads.xml 1438 - As we've already mentioned, each monad has its own - specialised evaluation functions. In the case of the state - monad, we have several to choose from. + As we've already mentioned, each monad has its + own specialised evaluation functions. In the case of the + state monad, we have several to choose from. hunk ./en/ch14-monads.xml 1444 - runState returns both the - result and the final state. + runState returns both + the result and the final state. hunk ./en/ch14-monads.xml 1448 - evalState returns only the - result, throwing away the final state. + evalState returns + only the result, throwing away the final state. hunk ./en/ch14-monads.xml 1452 - execState throws the result - away, returning only the final state. + execState throws the + result away, returning only the final state. hunk ./en/ch14-monads.xml 1464 + + Here's a complete example of how to run our + getTwoRandoms function. + + &Random.hs:runTwoRandoms; + + The call to runState follows a + standard pattern: we pass it a function in the state monad + and an initial state. It returns the result of the function + and the final state. + + The code surrounding the call to + runState merely obtains the current + global StdGen value, then replaces it + afterwards so that subsequent calls to + runTwoRandoms or other random + generation functions will pick up the updated state. + + + + What about a little more state? + + It's a little hard to imagine writing much interesting + code in which there's only a single state value to pass + around. When we want to track multiple pieces of state at + once, the usual trick is to maintain them in a data type. + Here's an example: keeping track of the number of random + numbers we are handing out. + + &Random.hs:CountedState; + + This example happens to consume both elements of the + state, and construct a completely new state, every time we + call into it. More frequently, we're likely to read or + modify only part of a state. This function gets the number + of random values generated so far. + + &Random.hs:getRandom; + + We deliberately used record syntax to define our + CountedRandom state, and this example should + make it clear why. It gives us accessor functions that we + can glue together with get to read + specific pieces of the state. + + If we want to partially update a state, the code doesn't + come out quite so appealingly. + + &Random.hs:putRandom; + + Here, instead of a function, we're using record update + syntax. The expression st { crCount = a } + creates a new value that's an identical copy of + st, except in its crCount + field, which is given the value a. + Because this is a syntactic hack, we don't get the same kind + of flexibility as with a function. Record syntax may not + exhibit Haskell's usual elegance, but it at least gets the + job done. hunk ./examples/ch14/Random.hs 47 -getTwoRandoms :: Random a => RandomState (a, a) -getTwoRandoms = do +getTwoRandomsUgly :: Random a => RandomState (a, a) +getTwoRandomsUgly = do hunk ./examples/ch14/Random.hs 58 +{-- snippet runTwoRandoms --} +runTwoRandoms :: IO (Int, Int) +runTwoRandoms = do + oldState <- getStdGen + let (result, newState) = runState getTwoRandoms oldState + setStdGen newState + return result +{-- /snippet runTwoRandoms --} + +{-- snippet CountedRandom --} +data CountedRandom = CountedRandom { + crGen :: StdGen + , crCount :: Int + } + +type CRState = State CountedRandom + +getCountedRandom :: Random a => CRState a +getCountedRandom = do + st <- get + let (val, gen') = random (crGen st) + put CountedRandom { crGen = gen', crCount = crCount st + 1 } + return val +{-- /snippet CountedRandom --} + +{-- snippet getCount --} +getCount :: CRState Int +getCount = crCount `liftM` get +{-- /snippet getCount --} + +{-- snippet putCount --} +putCount :: Int -> CRState () +putCount a = do + st <- get + put st { crCount = a } +{-- /snippet putCount --} + }