来自Haskell的State Monad的ID

可能重复:
在Haskell中创建独特的标签

我有一个数据类型Person和一些输入数据,我将从中创建Person。

我想让每个人都有自己的ID(比方说整数[0 ..])。 我可以用递归来做到这一点,但是因为我在Haskell中这样做,所以我想了解单子。 我想,Monad可能是这项工作的最佳选择。

事情是,我不是很懂很多东西:我什么时候进入monad(哪些函数可以使用内部函数),如何将它们放在一起,如何使'tick'函数前进等等。 。

所以我现在坚持这个:滴答功能可能有效,但我不知道如何使用它; 如何继续为人的建设创造价值。

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
  -- ???

编辑:使用Data.Unique或Data.Unique.Id会更容易吗? 它将如何用于我的情况?


那么,我认为最好的解释方法就是写一些代码。

首先,你也想隐藏你当前工作的monad的内部工作。 我们将使用类型别名来做到这一点,但是有更强大的方法,请参阅Real World Haskell的本章。

type PersonManagement = State Int

之所以这样做,是因为你以后会在PersonManagement中添加更多的东西,以及使用黑匣子抽象的好习惯。

与PersonManagement的定义一起,您应该公开定义此monad的基本操作。 在你的情况下,我们现在只有滴答功能,看起来几乎相同,但签名更清晰,名称更具提示性。

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

现在,所有上述应该驻留在一个单独的模块中。 除此之外,我们可以定义更复杂的操作,比如创建一个新的Person:

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

到目前为止,您可能已经意识到PersonManagement是一种计算类型,或者是封装用于处理Persons和PersonManagement Person逻辑的进程PersonManagement Person是一种计算,我们可以从中获取PersonManagement Person对象。 这非常好,但我们如何才能真正获得我们刚刚创建的人员并对他们做些什么,例如在控制台上打印他们的数据。 那么,我们需要一个“运行”方法,它运行我们的过程并给出结果。

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

runPersonManagement运行monad并在后台执行所有副作用时获得最终结果(在你的情况下,勾选Int状态)。 这使用来自状态monad的evalState,它也应该驻留在上面描述的模块中,因为它知道monad的内部工作方式。 我假定,你总是想从一个固定的值开始person id,由startState标识。

例如,如果我们想创建两个人并将它们打印到控制台,程序将如下所示:

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

输出:

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

由于PersonManagement是一个完整的monad,因此您还可以使用Control.Monad中的通用函数。 假设您想从名称列表中创建人员列表。 那么,这只是monad域中的地图函数 - 它被称为mapM。

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

用法:

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

例子可以继续。

要回答你的一个问题 - 只有当你需要由monad提供的服务时,你才能在PersonManagement monad中工作 - 在这种情况下,需要函数generatePersonId,或者需要函数,而函数又需要monad的原语,比如需要createPerson函数的work反过来需要在PersonManagement monad中运行,因为它需要自增计数器。 例如,如果您有一个检查两个人是否具有相同数据的函数,那么您不需要在PersonManagement monad中工作,它应该是Person -> Person -> Bool类型的普通纯函数。

要真正理解如何使用monads,你只需要通过大量的例子。 真实世界Haskell是一个很好的开始,学习你也是一个Haskell。

您还应该查看一些使用monad的库,了解它们是如何制作的以及人们如何使用它们。 一个很好的例子是解析器,parsec是一个很好的开始。

此外,P. Wadler撰写的这篇论文提供了一些非常好的例子,当然,还有更多的资源可以被发现。


在单子do在很多方面语法的工作相当“就像你所期望的”,把整个事情,如果它是命令式语言。

那么从程序上讲,我们想在这里做什么? 迭代给定的名字,对吧? 怎么样

forM names

与来自Control.Monad forM 。 就像你知道的那样,这几乎就像一个for循环。 好的,首先我们需要将每个名称绑定到一个变量

forM names $ thisName -> do

我们想要做什么? 我们需要一个ID, tick会为我们生成它

   newId <- tick

并将其与该人的姓名结合起来。 就是这样!

   return $ Person newId thisName

整个事情看起来像这样:

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

如预期那样工作,或者如果Ideone安装了mtl软件包,则会...


更好地处理mapAccumL类的问题

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

无论如何,我修改了你的程序,使它与状态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/42913.html

上一篇: IDs from State Monad in Haskell

下一篇: State Monad, sequences of random numbers and monadic code