问题
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?
回答1:
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.
回答2:
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...
回答3:
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
回答4:
The real difficulty here is defining and dealing with the scope over which identifiers are expected to be unique. Using the State
monad and a Succ
instance (as in my example below) can easily be massaged to guarantee uniqueness over the scope of a single State
monad computation. With a little extra care (capturing the final state after a runState
and making sure to use it as the initial state in the next runState
) you can guarantee uniqueness over multiple State
computations—but it's probably better just to compose the two computations into a larger one.
Data.Unique
and Data.Unique.Id
may seem easier, but there's two issues to keep in mind:
- Your code will be tied to the
IO
monad. - The
Unique
modules aren't explicit about the scope over which the generated IDs are unique. Does your program care whether the same ID might be assigned to two different persons in different runs of the program? Does your program rely on being able to "reinstate" the Person-to-ID assignments from previous executions?
Those are the questions I'd be thinking of before choosing between the alternatives here.
Anyway, here's my take on your code (completely untested, might not even compile, but you should get the idea):
import Control.Monad (mapM) -- Study the Control.Monad module carefully...
-- Your "tick" action can be made more generic by using `Enum` instead of numbers
postIncrement :: Enum s => State s s
postIncrement = do r <- get
put (succ r)
return r
-- Action to make a labeled Person from a name.
makePersonM :: String -> State Int Person
makePersonM name = do label <- postIncrement
return $ Person label name
-- The glue you're missing is mapM
whatYouWant = evalState (mapM makePersonM names) 0
来源:https://stackoverflow.com/questions/12941625/ids-from-state-monad-in-haskell