我描述了四种信息来源,您可以使用这些信息来了解 >>= 对特定 monad 的行为。
>>=的类型
>>= 的类型始终相同。它在Monad 类型类中指定。见documentation。类型是:
(>>=) :: forall a b. m a -> (a -> m b) -> m b
m 是您感兴趣的特定 monad 的占位符。例如,对于列表 monad,>>= 的类型为:
(>>=) :: forall a b. [a] -> (a -> [b]) -> [b]
请注意,我刚刚将 m ... 替换为 [...]。
单子定律
>>= 的实现对于每个 monad 都是不同的,但所有 monad 都应该遵守 monad 法则。这些定律在Monad 类型类的文档中指定。见documentation again。法律是:
return a >>= k == k a
m >>= return == m
m >>= (\x -> k x >>= h) == (m >>= k) >>= h
因此,无论某些特定 monad 的实现可能是什么,您都可以使用这些定律来推理您的代码。例如,如果您的代码包含类似法律左侧的代码,您可以将该代码替换为相应的法律右侧,并且行为不应改变。
这是一个如何使用单子定律的示例。假设我写了这段代码:
foo = do
x <- bar
return x
我们甚至不知道这里使用了什么 monad,但我们知道有一些 monad,因为我们看到了 do 表示法。要应用 monad 法则,我们必须对 >>= 的调用去除 do 符号:
foo = bar >>= (\x -> return x)
请注意,\x -> return x 与 return 相同(通过 η-reduction。
foo = bar >>= return
根据第二个单子定律,这段代码与调用 bar 的含义完全相同。
foo = bar
所以看起来好像原始foo 函数中的>>= 根本不能做任何有趣的事情,因为单子法则允许我们把它排除在外。我们甚至在不知道这里提供 >>= 运算符的特定 monad 的情况下就知道了这一点。
特定 monad 的文档
如果您需要了解更多关于特定 monad 的 >>= 的行为,特定 monad 的文档应该会告诉您。您可以使用hoogle 搜索文档。例如,documentation of StateT 告诉您:
return 函数保持状态不变,而>>= 使用第一个计算的最终状态作为第二个计算的初始状态。
特定monad的实现
如果您想了解有关特定 monad 实现的更多细节,您可能需要查看实际的实现。搜索 instance Monad ... 声明。例如,查看implementation of StateT。 list monad的实现在this file某处,搜索instance Monad []或者看看这个除了:
instance Monad [] where
m >>= k = foldr ((++) . k) [] m
m >> k = foldr ((++) . (\ _ -> k)) [] m
return x = [x]
fail _ = []
也许不是最明显的定义,但如果您为列表单子调用 >>=,就会发生这种情况。
总结
所有 monad 共享 >>= 和 return 的类型签名以及 monad 法则。除了这些限制之外,每个 monad 都提供了 >>= 和 return 的不同实现,如果您想了解所有细节,则必须研究 instance Monad ... 声明的源代码。如果您只想了解如何使用特定的 monad,请尝试查找一些相关文档。