为什么 (>>) 没有定义为 (*>)?
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 法则。因此,您建议的方法可能会使优化器更加努力地工作,并且在某些情况下甚至可能会阻止它生成最佳代码。
GHC 目前将 >>
实现为
(>>) :: m a -> m b -> m b
m >> k = m >>= \_ -> k
为什么不改为执行以下操作?
(>>) :: m a -> m b -> m b
m >> k = m *> k
现在,我在想 >>=
做了一些 *>
没有做的事情。
但是一切都在语法上有效(如在类型方面),所以真的很难解释为什么它不起作用。 也许 monad 实例会做一些应用实例不会做的计算,但我认为这会破坏类型的语义。
更新 我只能选择一个被接受的 SO 答案,但是
根据 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 法则。因此,您建议的方法可能会使优化器更加努力地工作,并且在某些情况下甚至可能会阻止它生成最佳代码。