Identifiers from Monada at Haskell

Possible duplicate:
Create unique shortcuts in Haskell

I have a Person data type and some input from which I will create Faces.

I would like each person to have their own identifier (let integers [0 ..]). I could do this with recursion, but since I do it in Haskell, I would like to understand monads. Is the state monad probably the best for this job?

The thing is, I really don’t understand many things: when I’m inside the monad (which functions can be used inside), how do I cheat them, how do I make the tick function in advance, etc ...

So, I'm currently stuck with this: the checkmark function probably works, but I'm not sure how to use it; and how to consistently receive your 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 it be somehow easier with Data.Unique or Data.Unique.Id? How will it be used in my situation?

0
source share
4 answers

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

First of all, you will want to hide too much the internal work of the monad in which you are currently working. We will 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 that you will add more things to PersonManagement later and its good practice to use black box abstraction.

Along with the definition of PersonManagement, you must expose the primitive operations that define this monad. In your case, we only have a checkmark function that 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 be in a separate module. In addition, we can define more complex operations, such as creating a new Person:

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

By now, you probably understood that PersonManagement is a type of calculation, or a process that encapsulates the logic for communicating with Persons, and PersonManagement Person is a calculation from which we get the person object. This is very nice, but how do we actually get the people we just created and do something with them, for example, print their data on the console. Well, we need a run method that starts our process and gives us the result.

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

RunPersonManagement launches the monad and gets the final result, performing all the side effects in the background (in your case, noting the status of Int). This uses evalState from the state monad, and it should also be in the module described above, since it knows about the internal operation of the monad. I suggested 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 people and print them on the console, the program would look something like this:

 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 common functions from Control.Monad . Suppose you want to create a list of faces from a list of names. Well, that only the map function is removed in the monad area - it is called mapM .

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

Using:

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

And examples can go on.

To answer one of your questions - you work in the PersonManagement monad only when you need the services provided by this monad - in this case, the generatePersonId function or you need functions that in turn require monad primitives, such as work , which the function needs createPerson , which, in turn, must be run inside the PersonManagement monad, because it needs a counter with an individual increase. If you have, for example, a function that checks whether two people have the same data, you will not need to work inside the PersonManagement monad, and this should be a normal pure function like Person -> Person -> Bool .

To really understand how to work with monads, you just need to go through many examples. The real world of Haskell is a great start, and so Teach you Haskell .

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

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

+5
source

Better done with mapAccumL as

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

In any case, I modified your program to make it a 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 
+1
source

The monad in the do syntax works in many ways quite “the way you expected”, treating it all as if it were an imperative language.

So what do we want to do here, procedurally? Iterating over these names, right? What about

 forM names 

with forM from Control.Monad . This is very similar to a for loop, as you know it. So first we need to associate each name with a variable

 forM names $ \thisName -> do 

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

  newId <- tick 

and combine it with the name of the person. What is it!

  return $ Person newId thisName 

it all looks like this:

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

which works as expected , or if mtl was installed on Ideone ...

+1
source

The real difficulty here is identifying and considering the area in which identifiers should be unique. Using an instance of State monad and a Succ (as in my example below) can be easily massaged to guarantee uniqueness in a single calculation of the State monad. With a little caution (capturing the final state after runState and ensuring that it is used as the initial state in the next runState ) you can guarantee uniqueness over several State calculations, but it is probably best to simply compose two calculations into larger ones.

Data.Unique and Data.Unique.Id may seem simpler, but there are two questions to keep in mind:

  • Your code will be bound to the IO monad.
  • Unique modules are not explicit in the area in which the generated identifiers are unique. Does your program save, can the same identifier be assigned to two different persons in different programs? Does your program mean the ability to "restore" Person-to-ID assignments from previous executions?

These are the questions that I would think about before choosing between the alternatives here.

In any case, here I take over your code (completely untested, it may not even compile, but you should get an 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 ss 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 
0
source

All Articles