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

时间:2017-11-15 21:06:29

标签: haskell functional-programming monads

考虑表达式:

[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

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

2 个答案:

答案 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的无限嵌套类型。