我认为你永远不需要单子。它们只是当您使用某些类型的功能时自然出现的一种模式。我见过的对这种观点的最佳解释是 Dan Piponi (sigfpe) 的优秀博客文章“你本可以发明单子!(也许你已经有了。)”,这个答案的灵感来自。
你说你写了一个没有使用状态单子的游戏。它看起来像什么?很有可能您最终使用的函数类型类似于openChest :: Player -> Location -> (Item,Player)
(打开一个箱子,可能用陷阱伤害玩家,并返回找到的物品)。一旦你需要组合它们,你可以手动(let (item,player') = openChest player loc ; (x,player'') = func2 player' y in ...
)或重新实现 state monad 的>>=
操作符。
或者假设我们正在使用带有哈希映射/关联数组的语言,而我们没有使用 monad。我们需要查找一些项目并使用它们;也许我们正试图在两个用户之间发送消息。
send username1 username2 = {
user1 = users[username1]
user2 = users[username2]
sendMessage user1 user2 messageBody
}
但是等等,这行不通;username1
并且username2
可能会丢失,在这种情况下,它们将是nil
or-1
或其他东西而不是所需的值。或者也许在关联数组中查找一个键会返回一个类型的值Maybe a
,所以这甚至会是一个类型错误。相反,我们必须写一些类似的东西
send username1 username2 = {
user1 = users[username1]
if (user1 == nil) return
user2 = users[username2]
if (user2 == nil) return
sendMessage user1 user2 messageBody
}
或者,使用Maybe
,
send username1 username2 =
case users[username1] of
Just user1 -> case users[username2] of
Just user2 -> Just $ sendMessage user1 user2 messageBody
Nothing -> Nothing
Nothing -> Nothing
哎呀!这是混乱和过度嵌套的。所以我们定义了某种函数,它结合了可能失败的动作。也许像
(>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
f >>= Just x = f x
f >>= Nothing = Nothing
所以你可以写
send username1 username2 =
users[username1] >>= $ \user1 ->
users[username2] >>= $ \user2 ->
Just (sendMessage user1 user2 messageBody)
如果你真的不想使用Maybe
,那么你可以实现
f >>= x = if x == nil then nil else f x
同样的原则也适用。
不过,真的,我建议阅读“你本可以发明单子!” 这是我对单子的直觉,并更好,更详细地解释它的地方。当处理某些类型时,Monads 会自然而然地出现。有时你把这个结构明确化,有时你没有,但仅仅因为你克制它并不意味着它不存在。从不需要使用特定结构的意义上说,您永远不需要使用 monad ,但通常这是很自然的事情。并且认识到这里的通用模式,就像在许多其他事情中一样,可以让你编写一些很好的通用代码。
(另外,正如我使用的第二个示例所示,请注意,您已经通过替换Maybe
魔法值将婴儿与洗澡水一起扔了出去。仅仅因为Maybe
是单子并不意味着您必须像使用单子一样使用它;列表也是单子,函数(形式r ->
)也是如此,但你不建议摆脱它们!:-))