IDs from State Monad in Haskell [duplicate]

倾然丶 夕夏残阳落幕 提交于 2020-01-16 19:00:48

问题


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:

  1. Your code will be tied to the IO monad.
  2. 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!