考虑表达式:
[0,1..] >>= \i -> [i * 2]
在List的>>=
定义中,lambda函数\i -> [i * 2]
通过fmap映射到list参数,从而生成列表列表[[0], [2]..]
。因此>>=
需要使用连接函数展平结果才能返回列表:[0, 2..]
根据this source:“......根据fmap和连接定义绑定适用于每个monad m:ma >>= k = join $ fmap k ma
”
那么为什么有必要将monad的负担放在提供给>> =的函数上?为什么不简单地定义bind呢?
ma >>= k = fmap k ma
这样您就不必处理扁平化结果。
答案 0 :(得分:14)
您提议的是简单地将绑定运算符定义为等于fmap
,但是使用参数交换:
ma >>= k = fmap k ma
-- is equivalent to:
(>>=) = flip fmap
在哪种情况下,为什么不只使用fmap
本身或其运算符形式<$>
?
(*2) <$> [0,1..]
> [0,2,4,6,...]
但是,这不包括所有可能使用bind
的情况。不同之处在于monad比functor更强大。仿函数只允许你为每个输入生成一个输出,monads允许你做各种疯狂的事情。
例如,考虑以下因素:
[0,1,2,3] >>= \i -> [i*2, i*3]
> [0,0,2,3,4,6,6,9]
这里,函数为每个输入生成两个值。这不能仅通过fmap
来表达。这需要join
生成的值。
这是另一个更不明显的例子:
[0,1,2,3,4,5] >>= \i -> if i `mod` 2 == 0 then [] else [i]
> [1,3,5]
这里,函数产生一个值或不产生值。从技术上讲,空列表仍然是List monad中的值,但无法通过fmap
输入获取它。
答案 1 :(得分:1)
monad的一个主要特点是能够“平坦化”#34; (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
的无限嵌套类型。