我刚刚了解了monad,我一直在尝试在Control.Monad中实现很多功能。我刚到ap
,但我无法使其发挥作用。我创建了一个函数almostAp :: Monad m => m (a -> b) -> m a -> m (m b)
,并尝试使用我创建的另一个函数flatten :: Monad m => m (m b) -> m b
来组合它。问题是当我尝试使用ap = flatten . almostAp
时,我得到了
Occurs check: cannot construct the infinite type: m ~ (->) (m a)
Expected type: m (a -> b) -> m a -> m a -> m b
Actual type: m (a -> b) -> m a -> m (m b)
In the second argument of ‘(.)’, namely ‘almostAp’
In the expression: (flatten . almostAp)`
但是,根据ghci,(flatten .)
的类型为Monad m => (a -> m (m b)) -> a -> m b
,为什么会发生这种情况呢?
以下是函数定义(我知道我可以使用=<<
和函子定律来清理它们):
almostAp :: Monad m => m (a -> b) -> m a -> m (m b)
almostAp = (flip (\x -> fmap ($x))) . (fmap (flip (>>=))) . (fmap (return .))
flatten :: Monad m => m (m a) -> m a
flatten = (>>= id)
答案 0 :(得分:5)
您收到了类型错误,因为您尝试撰写单参数函数flatten
(顺便提一句,通常名称为join
)一个两个参数函数almostAp
。 (.) :: (b -> c) -> (a -> b) -> (a -> c)
用于当右边的函数是单参数函数时。不幸的是,错误消息不是很有帮助。
(.).(.)
(发音为“ dot -dot- dot ”或“胸部”)可以满足您的需求:
ghci> let ap = ((.).(.)) flatten almostAp
ghci> :t ap
ap :: Monad m => m (a -> b) -> m a -> m b
但使用ap
表示法可以更简单地实现do
。你的版本真的比这更容易理解吗?
ap mf mx = do
f <- mf
x <- mx
return (f x)
do
符号只是>>=
的语法糖。如果没有它,它的外观如何(虽然我更喜欢do
版本):
ap mf mx = mf >>= \f -> fmap f mx
答案 1 :(得分:3)
但是,根据ghci,
(flatten .)
的类型为Monad m => (a -> m (m b)) -> a -> m b
,为什么会发生这种情况呢?
确实如此。考虑:
flatten :: Monad m => m (m a) -> m a
almostAp :: Monad m => m (a -> b) -> m a -> m (m b)
因此,(flatten .)
的类型是:
flatten :: Monad m => m (m a) -> m a -- renaming a to b
| | | |
------- ---
| |
(.) :: (b -> c) -> (a -> b) -> a -> c
| |
------- ---
| | | |
(flatten .) :: Monad m => (a -> m (m b)) -> a -> m b
但是,您无法将(flatten .)
应用于almostAp
,因为这些类型不兼容:
almostAp :: Monad m => m (a -> b) -> m a -> m (m b)
| | | |
---------- --------------
| |
| -------
| | |
(flatten .) :: Monad m => ( a -> m (m b)) -> a -> m b
你期待这个:
almostAp :: Monad m => m (a -> b) -> m a -> m (m b)
| | | |
----------------- -------
| |
| -------
| | |
(flatten .) :: Monad m => ( a -> m (m b)) -> a -> m b
但是,这不是currying的工作方式。类型a -> b -> c
的函数表示a -> (b -> c)
而不是(a -> b) -> c
。第一个函数a -> (b -> c)
接受两个参数 1 ,a
和b
,并返回c
。第二个函数(a -> b) -> c
接受一个参数a -> b
并返回c
。
那么,你如何撰写flatten
和almostAp
?您无法使用正常的函数组合来执行此操作,因为almostAp
需要两个参数:
(.) :: (b -> c) -> (a -> b) -> a -> c
| | | |
-------- --------
| |
flatten +-- almostAp can't be used because it needs two arguments
-- but (.) only gives it one argument (the `a` in a -> b)
我们需要一个特殊的合成运算符来组合它们:
(.:) :: (c -> d) -> (a -> b -> c) -> a -> b -> d
| | | |
-------- -------------
| |
flatten almostAp
(.:) f g x y = f (g x y)
现在,我们可以简单地写flatten .: almostAp
。另一种写作方式是(flatten .) . almostAp
。这是因为(.:) = (.) . (.)
。请阅读以下内容以获取更多详细信息:
What does (f .) . g mean in Haskell?
1 实际上,a -> (b -> c)
类型的函数只接受一个参数a
并返回另一个函数b -> c
,它接受第二个参数{{1}并返回b
。