如何理解 "ap = liftM2 id" 的类型推断?

How to understand type inference of "ap = liftM2 id"?

id     :: a -> a
liftM2 :: (Monad m) => (a -> b -> c) -> m a -> m b -> m c

ap     :: (Monad m) => m (a -> b) -> m a -> m b    
ap     =  liftM2 id

你能帮忙解释一下当liftM2应用于id时,ap的类型是如何推断出来的吗?此外,提出等效问题是否有效,但更具体地说,在这种情况下 (a -> b -> c) -> m a 是如何减少到 m (a -> b) 的?

让我们试着找出 liftM2 id 的类型应该是什么。首先,我们更改 id 中的类型参数,以便我们可以更轻松地解决此问题。

id     :: x -> x
liftM2 :: (Monad m) => (a -> b -> c) -> m a -> m b -> m c

接下来,我们在 liftM2 中添加额外的括号并记住 a -> b -> c 实际上是 a -> (b -> c):

id     :: x -> x
liftM2 :: (Monad m) => (a -> (b -> c)) -> m a -> (m b -> m c)

现在我们移动 x -> x 使其与 liftM2 中的其他类型对齐:

id     ::               x -> x
liftM2 :: (Monad m) => (a -> (b -> c)) -> m a -> (m b -> m c)

好的。这告诉我们 a ~ (b -> c) in liftM2 id,或者:

id     ::               (b -> c) -> (b -> c)
liftM2 :: (Monad m) => ((b -> c) -> (b -> c)) 
                    -> m (b -> c) -> (m b -> m c)

现在我们可以使用那些专门的版本:

liftM2 id :: Monad m => m (b -> c) -> (m b -> m c)

我们去掉多余的括号,得到正确的类型:

liftM2 id :: Monad m => m (b -> c) -> m b -> m c

id 的类型为 a -> a。第一个问题是我们如何统一 a -> aliftM2的参数类型(a -> b -> c)?诀窍是将 a -> a 中的 a 替换为 (a -> b),从而得到 (a -> b) -> (a -> b) 或等效的 (a -> b) -> a -> b。 (顺便说一句,这是 $ 的类型,这意味着 $ 只是 id 的受限类型!)

现在我们将 (a -> b) -> a -> bliftM2 的整个类型结合起来:

Monad m => (a1 -> a2 -> r) -> m a1 -> m a2 -> m r

我们将 a1 替换为 a -> b,将 a2 替换为 a,将 r 替换为 b,从而得到:

Monad m => ((a -> b) -> a -> b) -> m (a -> b) -> m a -> m b

最后,一旦我们将 liftM2 应用于 id,结果具有相同的类型减去第一个参数:

liftM2 id :: Monad m => m (a -> b) -> m a -> m b

我们有:ap 的类型。

对此的一个很好的直觉是基于我之前对 $ 的观察。 $是普通的函数应用操作符; ap 是函数应用程序 在 monad 上的提升。 liftM2 ($) 给你 ap 是有道理的,因为这就是 ap 的根本含义......而 id 只是 $ 的一个版本,具有更通用的类型.