嗯,我认为最好的解释方式就是写一些代码。
首先,你也想隐藏你当前工作的 monad 的内部工作。我们将使用类型别名来执行此操作,但还有更强大的方法,请参阅Real World Haskell 中的本章。
type PersonManagement = State Int
这样做的原因是为了防止您稍后向 PersonManagement 添加更多内容,以及使用黑盒抽象的良好做法。
连同 PersonManagement 的定义,您应该公开定义此 monad 的原始操作。就您而言,我们目前只有 tick 函数,看起来几乎相同,但签名更清晰,名称更具暗示性。
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 是一种计算类型,或者是封装处理人员的逻辑的过程,PersonManagement Person 是我们从中获取人员对象的计算。这非常好,但是我们如何真正获取我们刚刚创建的人并对他们做一些事情,比如在控制台上打印他们的数据。好吧,我们需要一个“运行”方法,它运行我们的过程并给我们结果。
runPersonManagement :: PersonManagement a -> a
runPersonManagement m = evalState m startState
runPersonManagement 运行 monad 并获得最终结果,同时在后台执行所有副作用(在您的情况下,勾选 Int 状态)。这使用来自状态 monad 的evalState,它也应该驻留在上述模块中,因为它知道 monad 的内部工作原理。我假设您总是希望从一个固定值开始人员 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 领域中提升的 map 函数——它被称为 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 的原语,如 work需要 createPerson 函数,该函数又需要在 PersonManagement monad 中运行,因为它需要自增计数器。例如,如果你有一个检查两个人是否有相同数据的函数,你就不需要在 PersonManagement monad 中工作,它应该是一个普通的、纯粹的 Person -> Person -> Bool 类型的函数。
要真正了解如何使用 monad,您只需阅读大量示例即可。 Real World Haskell 是一个很好的开始,Learn you a Haskell 也是如此。
您还应该查看一些使用 monad 的库,以了解它们是如何制作的以及人们如何使用它们。解析器就是一个很好的例子,parsec 是一个很好的起点。
此外,P. Wadler 的 paper 提供了一些非常好的示例,当然,还有更多资源可供发现。