我试图理解为什么在下面序列的最后一行添加id
会消除单子现象:
Prelude> :t id
id :: a -> a
Prelude> :t Control.Monad.liftM2
Control.Monad.liftM2
:: Monad m => (a1 -> a2 -> r) -> m a1 -> m a2 -> m r
Prelude> :t (==)
(==) :: Eq a => a -> a -> Bool
Prelude> :t Control.Monad.liftM2 (==)
Control.Monad.liftM2 (==)
:: (Monad m, Eq a) => m a -> m a -> m Bool
Prelude> :t Control.Monad.liftM2 (==) id
Control.Monad.liftM2 (==) id :: Eq a => (a -> a) -> a -> Bool
Prelude>
添加id :: a -> a
如何以最后一行中的方式更改签名?
答案 0 :(得分:8)
您正在将类型固定为特定的Monad
实例,即“函数阅读器”单子(instance Monad ((->) a)
)。
id :: a -> a
,而您试图将其用作类型m a
的参数的参数,因此:
m a ~ a -> a
m a ~ (->) a a
m a ~ ((->) a) a
m ~ (->) a
a ~ a
签名的其余部分是:
m a -> m Bool
从m ~ (->) a
开始,结果类型为:
(->) a a -> (->) a Bool
(a -> a) -> (a -> Bool)
(a -> a) -> a -> Bool
(加上使用Eq a
的{{1}}约束。)
这在无点代码中非常有用,尤其是在使用==
实例的情况下,因为您可以将函数的参数隐式地“扩展”到子计算:
Applicative
答案 1 :(得分:3)
3
的签名为liftM2 (==)
。因此,这意味着如果我们以(Monad m, Eq a) => m a -> m a -> m Bool
作为参数调用此函数,则意味着id :: b -> b
和m a
是同一类型。
b -> b
成立的事实并不成问题,因为m ~ (->) b
是(->) r
的一个实例,实际上在GHC.Base source code中我们看到:
Monad
这仅在-- | @since 2.01
instance Monad ((->) r) where
f >>= k = \ r -> k (f r) r
时才有意义。这里的箭头m ~ (->) b
是类型构造函数,(->)
与(->) a b
相同。
因此,这意味着如果我们计算a -> b
的类型,则会得出以下信息:
liftM2 (==) id
因此,这意味着liftM2 (==) :: m a -> m a -> m Bool
id :: (b -> b)
-------------------------------------------
m ~ (->) b, a ~ b
的输出类型为liftM2 (==) id
,但是我们需要使用我们获得的知识来“专门化”此类型:liftM2 (==) id :: (Monad m, Eq a) => m a -> m Bool
是m a
,并且(->) b
与a
的类型相同,因此:
b
简而言之,该功能仍然是“ monadic”的,尽管通过使用 liftM2 (==) id :: (Monad m, Eq a) => m a -> m Bool
-> liftM2 (==) id :: (Monad m, Eq a) => (b -> a) -> (b -> Bool)
-> liftM2 (==) id :: Eq b => (b -> b) -> (b -> Bool)
-> liftM2 (==) id :: Eq b => (b -> b) -> b -> Bool
您已经选择了一个特定的monad,因此该功能不再适用于所有类型的monad,仅适用于{{1} } monad。