IDs from State Monad in Haskell

Possible Duplicate:
Creating unique labels in Haskell

I've got a datatype Person and some input data from which I will create the Persons.

I'd like to have each Person have its own ID (let's say integers [0..]). I could do this with recursion, but since I'm doing this in Haskell, I'd like to understand the monads. The State Monad is probably the best for this job, I suppose?

The thing is, I don't really understand lots of things: when am I inside the monad (what functions can use the inside), how do I plumb them together, how do I make the 'tick' function advance, etc...

So I'm currently stuck with this: the tick function probably works, but I'm not sure how to use it; and how to successively get its value for the construction of Persons.

import Control.Monad.State

data Person = Person {
  id   :: Int,
  name :: String
} deriving Show

type MyState = Int
startState = 0

tick :: State MyState Int
tick = do
  n <- get
  put (n+1)
  return n

names = ["Adam","Barney","Charlie"]

-- ??? -> persons = [Person 0 "Adam", Person 1 "Barney", Person 2 "Charlie"]

main = do
  print $ evalState tick startState
  -- ???

EDIT: would this be somehow easier with Data.Unique or Data.Unique.Id? How would it be used in my situation?


Well, I think the best way to explain is to just write some code.

First of all, you would want too hide the inner workings of the monad in which you currently work. We'll do this with a type alias, but there are more powerful ways, see this chapter from Real World Haskell.

type PersonManagement = State Int

The reason for this is in case you'll add more things to PersonManagement later and its good practice to use the black box abstraction.

Together with the definition of PersonManagement, you should expose the primitive operations that define this monad. In your case, we have only the tick function for now which looks almost the same, but with a clearer signature and a more suggestive name.

generatePersonId :: PersonManagement Int
generatePersonId = do
    n <- get
    put (n+1)
    return n

Now, all of the above should reside in a separate module. On top of this we can define more complex operations, like the creation of a new Person:

createPerson :: String -> PersonManagement Person
createPerson name = do
    id <- generatePersonId
    return $ Person id name

By now you probably realized that PersonManagement is a type of computation, or a process which encapsulates logic for dealing with Persons and PersonManagement Person is a computation from which we obtain a person object. That's very nice, but how do we actually get the persons we just created and do something with them, like printing their data at the console. Well, we need a "run" method, which runs our process and gives us the result.

runPersonManagement :: PersonManagement a -> a
runPersonManagement m = evalState m startState

The runPersonManagement runs the monad and gets the final result while performing all the side effects in the background (in your case, ticking the Int state). This uses the evalState from the state monad, and it should also reside in the module described above since it knows about the inner workings of the monad. I assumed, that you always want to start the person id from a fixed value, identified by startState.

So for example if we wanted to create two persons and print them to the console, the program would be something like:

work :: PersonManagement (Person, Person)
work = do
    john <- createPerson "John"
    steve <- createPerson "Steve"
    return (john, steve)

main = do
    let (john, steve) = runPersonManagement work
    putStrLn $ show john
    putStrLn $ show steve

Output:

Person {id = 0, name = "John"}
Person {id = 1, name = "Steve"}

Since PersonManagement is a full fledged monad you can also use generic functions from Control.Monad for example. Let's say you want to create a list of persons from a list of names. Well, that's just the map function lifted in the domain of monads - it's called mapM.

createFromNames :: [String] -> PersonManagement [Person]
createFromNames names = mapM createPerson names

Usage:

runPersonManagement $ createFromNames ["Alice", "Bob", "Mike"] =>
    [
        Person {id = 0, name = "Alice"},
        Person {id = 1, name = "Bob"},
        Person {id = 2, name = "Mike"}
    ]

And examples could go on.

To answer one of your questions - you work in the PersonManagement monad only when you need the services provided by that monad - in this case, the generatePersonId function or you need functions which in turn require the monad's primitives like work which needs the createPerson function which in turn needs to run inside the PersonManagement monad because it needs the self-incrementing counter. If you have, for example, a function that checks whether two persons have the same data, you wouldn't need to work inside the PersonManagement monad and it should be a normal, pure function of type Person -> Person -> Bool .

To really understand how to work with monads you'll just have to go through a lot of examples. Real World Haskell is a great start and so is Learn you a Haskell.

You should also look into some libraries which use monads to see how they are made and how people use them. One great example are parsers, and parsec is a great place to start.

Also, this paper by P. Wadler provides some very nice examples and of course, there are many more resources that are ready to be discovered.


Monads in do syntax work in many ways quite "just like you'd expect", treating the whole thing as if it was an imperative language.

So what do we want to do here, procedurally speaking? Iterate over the given names, right? How about

forM names

with forM from Control.Monad . That's pretty much like a for loop as you know it. Ok, first we need to bind each name to a variable

forM names $ thisName -> do

What would we like to do? We need an ID, tick will generate it for us

   newId <- tick

and combine it with the person's name. And that's it!

   return $ Person newId thisName

the whole thing then looks like this:

(persons, lastId) = (`runState` startState) $ do
   forM names $ thisName -> do
      newId <- tick
      return $ Person newId thisName

which works as expected, or would if Ideone had the mtl package installed...


Better to do with mapAccumL like

getPersons = snd . mapAccumL f 0
    where
        f n name = (n+1,Person n name)

Anyways I modified your program to make it do with state monad

import Control.Monad.State

data Person = Person {
  id   :: Int,
  name :: String
} deriving Show

type MyState = Int
startState = 0

tick :: State MyState Int
tick = do
  n <- get
  put (n+1)
  return n

getPerson :: String -> State MyState Person
getPerson ps = do
  n <- tick
  return (Person n ps)


names = ["Adam","Barney","Charlie"]

getPersonsExample :: State MyState [Person]
getPersonsExample = do
    a <- getPerson "Adam"
    b <- getPerson "Barney"
    c <- getPerson "Charlie"
    return ([a,b,c])

main1 = do
  print $ evalState (sequence $ map getPerson names) startState

main2 = do
  print $ evalState getPersonsExample startState
链接地址: http://www.djcxy.com/p/42914.html

上一篇: 为什么我们需要monads?

下一篇: 来自Haskell的State Monad的ID