为什么 (>>) 没有定义为 (*>)?

Why is (>>) not defined as (*>)?

GHC 目前将 >> 实现为

(>>) :: m a -> m b -> m b
m >> k = m >>= \_ -> k

为什么不改为执行以下操作?

(>>) :: m a -> m b -> m b
m >> k = m *> k

现在,我在想 >>= 做了一些 *> 没有做的事情。

但是一切都在语法上有效(如在类型方面),所以真的很难解释为什么它不起作用。 也许 monad 实例会做一些应用实例不会做的计算,但我认为这会破坏类型的语义。

更新 我只能选择一个被接受的 SO 答案,但是 非常有见地(特别是对于像我这样相对缺乏经验的人在 Haskell).

根据 source code 中的评论,这是为了防止人们在 Applicative 实例中将 *> 重写为 >> 时意外创建递归绑定。

(>>)        :: forall a b. m a -> m b -> m b
m >> k = m >>= \_ -> k -- <b>See Note [Recursive bindings for Applicative/Monad]</b>

注释说:

Note: Recursive bindings for Applicative/Monad

The original Applicative/Monad proposal stated that after implementation, the designated implementation of (>>) would become

(>>) :: forall a b. m a -> m b -> m b
(>>) = (*>)

by default. You might be inclined to change this to reflect the stated proposal, but you really shouldn't! Why? Because people tend to define such instances the other way around: in particular, it is perfectly legitimate to define an instance of Applicative (*>) in terms of (>>), which would lead to an infinite loop for the default implementation of Monad! And people do this in the wild.

This turned into a nasty bug that was tricky to track down, and rather than eliminate it everywhere upstream, it's easier to just retain the original default.

4castle的回答当然是对的,但是还有一点需要考虑。并非每个 Monad 实例都支持比 liftA2 = liftM2 更高效的 Applicative 实例。也就是说,

liftA2 f xs ys = xs >>= \x -> ys >>= \y -> pure (f x y)

使用默认值 (*>) = liftA2 (flip const) 得到

xs *> ys = xs >>= \ _ -> ys >>= \y -> pure y

另一方面,(>>)的默认定义给出

xs >> ys = xs >>= \ _ -> ys

如您所见,这只使用了一个绑定,而另一个使用了两个。根据 monad 身份法,它们是等价的,但编译器不知道 monad 法则。因此,您建议的方法可能会使优化器更加努力地工作,并且在某些情况下甚至可能会阻止它生成最佳代码。