Mar*_*zek 0 monads haskell uniqueidentifier state-monad
可能重复:
在Haskell中创建唯一标签
我有一个数据类型Person和一些输入数据,我将从中创建人员.
我想让每个人都有自己的ID(假设整数[0 ..]).我可以通过递归来做到这一点,但是因为我在Haskell中这样做,所以我想了解monad.我想,State 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
-- ???
Run Code Online (Sandbox Code Playgroud)
编辑:使用Data.Unique或Data.Unique.Id会更容易吗?如何在我的情况下使用它?
好吧,我认为最好的解释方法就是编写一些代码.
首先,你想隐藏你当前工作的monad的内部工作方式.我们将使用类型别名执行此操作,但有更强大的方法,请参阅Real World Haskell的本章.
type PersonManagement = State Int
Run Code Online (Sandbox Code Playgroud)
这样做的原因是以后你会向PersonManagement添加更多东西,以及使用黑盒抽象的好习惯.
与PersonManagement的定义一起,您应该公开定义此monad的基本操作.在你的情况下,我们现在只有tick函数看起来几乎相同,但是有更清晰的签名和更具暗示性的名称.
generatePersonId :: PersonManagement Int
generatePersonId = do
n <- get
put (n+1)
return n
Run Code Online (Sandbox Code Playgroud)
现在,以上所有内容都应该位于一个单独的模块中.除此之外,我们可以定义更复杂的操作,例如创建新Person:
createPerson :: String -> PersonManagement Person
createPerson name = do
id <- generatePersonId
return $ Person id name
Run Code Online (Sandbox Code Playgroud)
到目前为止,您可能已经意识到PersonManagement是一种计算类型,或者是一个封装用于处理Persons的逻辑的过程,并且PersonManagement Person是我们从中获取person对象的计算.这是非常好的,但我们如何实际获得我们刚创建的人并使用它们做一些事情,比如在控制台上打印他们的数据.好吧,我们需要一个"运行"方法,它运行我们的过程并给我们结果.
runPersonManagement :: PersonManagement a -> a
runPersonManagement m = evalState m startState
Run Code Online (Sandbox Code Playgroud)
runPersonManagement运行monad并在后台执行所有副作用时获得最终结果(在您的情况下,勾选Int状态).这使用来自状态monad 的evalState,它也应该驻留在上面描述的模块中,因为它知道monad的内部工作方式.我假设你总是希望从一个由startState标识的固定值启动person id.
因此,例如,如果我们想要创建两个人并将它们打印到控制台,程序将是这样的:
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
Run Code Online (Sandbox Code Playgroud)
输出:
Person {id = 0, name = "John"}
Person {id = 1, name = "Steve"}
Run Code Online (Sandbox Code Playgroud)
由于PersonManagement是一个完整的monad,你也可以使用Control.Monad中的泛型函数.假设您要从名单列表中创建人员列表.好吧,那只是在monad领域中提升的地图功能 - 它被称为mapM.
createFromNames :: [String] -> PersonManagement [Person]
createFromNames names = mapM createPerson names
Run Code Online (Sandbox Code Playgroud)
用法:
runPersonManagement $ createFromNames ["Alice", "Bob", "Mike"] =>
[
Person {id = 0, name = "Alice"},
Person {id = 1, name = "Bob"},
Person {id = 2, name = "Mike"}
]
Run Code Online (Sandbox Code Playgroud)
例子可以继续下去.
要回答你的一个问题 - 只有当你需要monad提供的服务时才能在PersonManagement monad中工作 - 在这种情况下,你需要使用generatePersonId函数或者你需要函数,而这些函数又需要monad的原语,就像work需要createPerson函数一样需要在PersonManagement monad中运行,因为它需要自增量计数器.例如,如果你有一个检查两个人是否拥有相同数据的函数,你就不需要在PersonManagement monad中工作,它应该是一个普通的纯函数类型Person -> Person -> Bool.
要真正了解如何使用monad,您只需要经过大量示例.真实世界Haskell是一个很好的开始,所以是了解你的Haskell.
您还应该查看一些使用monad的库,以了解它们是如何制作的以及人们如何使用它们.一个很好的例子是解析器,而parsec是一个很好的起点.
此外,P. Wadler的这篇论文提供了一些非常好的例子,当然,还有更多的资源可供发现.