为什么这个应用实例是非法的?

Why is this applicative instance unlawful?

我正在阅读有关 monad 转换器的文章,发现了这篇显然很有名的文章 - A Gentle Introduction to Monad Transformers。引起我注意的是作者描述了一个临时 ExceptT 变压器的应用实例的部分,但留下了一个警告,指出该实例是非法的。

代码如下:

data EitherIO e a = EitherIO {
    runEitherIO :: IO (Either e a)
}

instance Functor (EitherIO e) where
    fmap f = EitherIO . fmap (fmap f) . runEitherIO

instance Applicative (EitherIO e) where
    pure    = EitherIO . return . Right
    f <*> x = EitherIO $
        liftA2 (<*>)
            (runEitherIO f)
            (runEitherIO x)

和警告:

Warning: A very sharp-sighted reader has pointed out to me that this applicative instance is unlawful. Specifically, it executes the side effects of the right-hand side unconditionally. The expectation of a lawful instance is that it should only execute the side-effects of the right-hand side if the left-hand side was a successful operation.

我假设 <*> 的具体实施是问题所在。

所以我的主要问题是:这个实例到底不满足哪些法则?

据我所见,四个适用定律都得到满足(当然我可能是错的)。作者说问题是即使左侧操作不成功(我假设 "successful operation" 表示 IO 操作在执行时会产生 Right 值。

虽然我认为从使用的角度来看它是有道理的,但看看这里究竟有哪些法则不满足以及为什么不满足仍然很有启发性。

此外,为什么实例非法的解释提到了副作用,这使得推理只适用于 IO monad?但在文本的最后,作为收尾,我们将 IO monad 更改为通用 monad,并使其成为所描述数据类型的参数。这引出了另一个问题:如果我们想象我自己正在编写这个 monad 转换器,我需要应用什么样的推理才能注意到所描述的应用实例确实是非法的,而无需诉诸思考可以与此转换器一起使用的特定单子?

Applicative实例是合法的。事实上,它与 Compose IO (Either e) 是同一个实例。 applicative functor 的组合是 applicative 的(这是 monad 没有的 applicatives 所具有的非常好的东西之一)。

但是,the documentation 的法律部分还列出了以下内容:

If f is also a Monad, it should satisfy

pure = return
(<*>) = ap
(*>) = (>>)

这就是问题所在,因为没有对应于给定应用程序的单子(这就是关于 RHS 的条件执行的注释发挥作用的地方)。所以 ApplicativeMonad 实例,虽然每个单独隔离都是合法的,但不同意,将被处以死刑。