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
来表达。这需要 join
ing 结果值。
这是另一个更不明显的例子:
[0,1,2,3,4,5] >>= \i -> if i `mod` 2 == 0 then [] else [i]
> [1,3,5]
这里,函数要么产生一个值,要么不产生一个值。空列表在技术上仍然是 List monad 中的一个值,但它不能通过 fmap
ing 输入获得。
考虑表达式:
[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
来表达。这需要 join
ing 结果值。
这是另一个更不明显的例子:
[0,1,2,3,4,5] >>= \i -> if i `mod` 2 == 0 then [] else [i]
> [1,3,5]
这里,函数要么产生一个值,要么不产生一个值。空列表在技术上仍然是 List monad 中的一个值,但它不能通过 fmap
ing 输入获得。