好吧,我认为最好的解释方法就是编写一些代码。
首先,你也想隐藏你当前工作的 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 是一种计算类型,或者是一个封装了处理 Person 的逻辑的过程,并且PersonManagement Person
是我们从中获取 person 对象的计算。这非常好,但是我们如何真正获取我们刚刚创建的人并对他们做一些事情,比如在控制台上打印他们的数据。好吧,我们需要一个“运行”方法,它运行我们的过程并给我们结果。
runPersonManagement :: PersonManagement a -> a
runPersonManagement m = evalState m startState
runPersonManagement 运行 monad 并在后台执行所有副作用时获得最终结果(在您的情况下,勾选 Int 状态)。这使用了state monad 中的evalState,它也应该驻留在上述模块中,因为它知道 monad 的内部工作原理。我假设,您总是希望从一个由 startState 标识的固定值开始人员 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
输出:
Person {id = 0, name = "John"}
Person {id = 1, name = "Steve"}
由于 PersonManagement 是一个成熟的 monad,您还可以使用Control.Monad中的通用函数,例如。假设您想从姓名列表中创建人员列表。好吧,这只是在 monads 领域中提升的 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 中工作,它应该是一个普通的纯函数 type Person -> Person -> Bool
。
要真正了解如何使用 monad,您只需要阅读大量示例即可。Real World Haskell是一个很好的开始,Learn you a Haskell也是如此。
您还应该查看一些使用 monad 的库,以了解它们是如何制作的以及人们如何使用它们。解析器就是一个很好的例子,而parsec是一个很好的起点。
此外,P. Wadler 的这篇论文提供了一些非常好的示例,当然,还有更多资源可供发现。