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?