Haskell >>= 操作:为什么 return 另一个 Monad 需要函数参数?

Haskell >>= operation: why is the function argument required to return another Monad?

考虑表达式:

[0,1..] >>= \i -> [i * 2]

在 List 的 >>= 定义中,lambda 函数 \i -> [i * 2] 通过 fmap 映射到列表参数,从而产生列表列表 [[0], [2]..]。因此 >>= 需要使用连接函数将结果展平,以便 return 列表:[0, 2..]

根据 this source:“...根据 fmap 和 join 的绑定定义适用于每个 monad m:ma >>= k = join $ fmap k ma

那么为什么有必要将 returning monad 的负担放在提供给 >>= 的函数上?为什么不像这样简单地定义绑定呢?

ma >>= k = fmap k ma

这样您就不必处理扁平化结果。

monad的一大特点就是能够"flatten"(join)。这是定义一个好的组合形式所必需的。

考虑两个具有副作用的函数的组合,比如在 IO monad 中:

foo :: A -> IO B
bar :: B -> IO C

如果>>=只是fmap(没有最后的join)通过,合成

\a -> foo a >>= bar

表示

\a -> fmap bar (foo a)
-- i.e.
fmap bar . foo

这看起来确实是一个不错的构图,但不幸的是它的类型是 A -> IO (IO C)!如果我们不能展平嵌套的 IO,每个组合都会添加另一层,我们最终会得到 IO (IO (IO ...)) 形式的结果类型,显示组合的数量。

充其量,这会带来不便。在最坏的情况下,这会阻止递归,例如

loop = readLn >>= \x -> print x >>= \_ -> loop

因为它导致具有无限嵌套的类型 IO

您的建议是简单地将绑定运算符定义为等于 fmap,但交换参数:

ma >>= k = fmap k ma
-- is equivalent to:
(>>=) = flip fmap

在这种情况下,为什么不直接使用 fmap 本身或其运算符形式 <$>

(*2) <$> [0,1..]
> [0,2,4,6,...]

然而,这不包括 所有 可能使用 bind 的情况。区别在于 monad 比函子更强大。函子只允许你为每个输入产生一个输出,而单子让你做各种疯狂的事情。

例如,考虑以下内容:

[0,1,2,3] >>= \i -> [i*2, i*3]
> [0,0,2,3,4,6,6,9]

这里,函数为每个输入生成两个值。这不能仅通过 fmap 来表达。这需要 joining 结果值。

这是另一个更不明显的例子:

[0,1,2,3,4,5] >>= \i -> if i `mod` 2 == 0 then [] else [i]
> [1,3,5]

这里,函数要么产生一个值,要么不产生一个值。空列表在技术上仍然是 List monad 中的一个值,但它不能通过 fmaping 输入获得。