可能重复:在Haskell中创建唯一标签
我有一个数据类型Person和一些输入数据,我将从中创建人员 .
我想让每个人都有自己的ID(假设整数[0 ..]) . 我可以通过递归来做到这一点,但是因为我在Haskell中这样做,所以我想了解monad . 我想,State Monad可能是这项工作的最佳人选 .
问题是,我并不是很了解很多东西:当我在monad中时(什么功能可以使用内部),我如何将它们连接在一起,如何使'tick'功能提前等等 . .
所以我现在坚持这个:滴答功能可能有效,但我不知道如何使用它;以及如何相继获得人才建设的 Value .
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会更容易吗?如何在我的情况下使用它?
4 回答
好吧,我认为最好的解释方法就是编写一些代码 .
首先,你想隐藏你当前工作的monad的内部工作方式 . 我们将使用类型别名执行此操作,但有更强大的方法,请参阅Real World Haskell中的本章 .
这样做的原因是以后你会向PersonManagement添加更多东西,以及使用黑盒抽象的好习惯 .
与PersonManagement的定义一起,您应该公开定义此monad的基本操作 . 在你的情况下,我们现在只有tick函数看起来几乎相同,但是有更清晰的签名和更具暗示性的名称 .
现在,以上所有内容都应该位于一个单独的模块中 . 除此之外,我们可以定义更复杂的操作,例如创建新Person:
到目前为止,您可能已经意识到PersonManagement是一种计算类型,或者是一个封装用于处理Persons的逻辑的进程,而
PersonManagement Person
是我们从中获取person对象的计算 . 这是非常好的,但我们如何实际获得我们刚创建的人并使用它们做一些事情,比如在控制台上打印他们的数据 . 好吧,我们需要一个"run"方法,它运行我们的过程并给我们结果 .runPersonManagement运行monad并在后台执行所有副作用时获得最终结果(在您的情况下,勾选Int状态) . 这使用来自状态monad的evalState,它也应该驻留在上面描述的模块中,因为它知道monad的内部工作方式 . 我假设你总是希望从一个由startState标识的固定值启动person id .
因此,例如,如果我们想要创建两个人并将它们打印到控制台,程序将是这样的:
输出:
由于PersonManagement是一个完整的monad,你也可以使用Control.Monad中的泛型函数 . 让's say you want to create a list of persons from a list of names. Well, that'只是在monad域中解除的map函数 - 它被称为mapM .
用法:
例子可以继续下去 .
要回答你的一个问题 - 只有当你需要monad提供的服务时才能在PersonManagement monad中工作 - 在这种情况下,你需要使用generatePersonId函数或者你需要函数,而这些函数又需要像
work
这样需要createPerson
函数的monad原语 . 反过来需要在PersonManagement monad中运行,因为它需要自增量计数器 . 例如,如果你有一个检查两个人是否有相同数据的函数,你就不需要在PersonManagement monad中工作,它应该是Person -> Person -> Bool
类型的普通纯函数 .要真正了解如何使用monad,您只需要经过大量示例 . Real World Haskell是一个很好的开始,所以Learn you a Haskell .
您还应该查看一些使用monad的库,以了解它们是如何制作的以及人们如何使用它们 . 一个很好的例子是解析器,parsec是一个很好的起点 .
此外,P. Wadler的这个paper提供了一些非常好的例子,当然,还有更多的资源可供发现 .
do
语法中的Monads在很多方面都很有效_11343426_,将整个事情视为命令式语言 .那么从程序上讲,我们想做什么呢?迭代给定的名字,对吗?怎么样
来自
Control.Monad
的forM
. 那非常像for
循环如你所知 . 好的,首先我们需要将每个名称绑定到一个变量我们想做什么?我们需要一个ID,
tick
将为我们生成它并将其与此人的名字相结合 . 就是这样!
整个事情看起来像这样:
哪个works as expected,或者如果Ideone安装了mtl包...
更喜欢
mapAccumL
之类的无论如何我修改了你的程序,使其与状态monad一起使用
这里真正的困难是定义和处理预期标识符唯一的范围 . 使用
State
monad和Succ
实例(如下例所示)可以轻松地进行按摩,以保证单个State
monad计算范围内的唯一性 . 稍加注意(在runState
之后捕获最终状态并确保在下一个runState
中将其用作初始状态),您可以保证多个State
计算的唯一性 - 但是将两个计算组合成更大的计算可能更好一 .Data.Unique
和Data.Unique.Id
可能看起来更容易,但要记住两个问题:您的代码将绑定到
IO
monad .Unique
模块没有明确说明生成的ID唯一的范围 . 您的程序是否关心是否可以在程序的不同运行中将相同的ID分配给两个不同的人员?您的程序是否依赖于能够"reinstate"之前执行的人员到ID分配?在选择这些替代方案之前,这些是我想到的问题 .
无论如何,这是我对你的代码的看法(完全未经测试,可能甚至没有编译,但你应该得到这个想法):