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)
在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 -> a
与liftM2
的论证类型统一起来,(a -> b -> c)
?诀窍是将a -> a
中的a
替换为(a -> b)
给我们(a -> b) -> (a -> b)
或等效地(a -> b) -> a -> b
。(作为一个简洁的旁注,这是$
的类型,这意味着$
只是id
受限类型!
现在我们将(a -> b) -> a -> b
与liftM2
的整个类型结合起来:
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
是将函数应用程序提升到单子上。liftM2 ($)
给你ap
是有道理的,因为这就是ap
的根本含义......id
只是具有更通用类型的$
版本。