尝试编写monadic函数时出错

时间:2016-04-10 23:26:11

标签: haskell monads

我刚刚了解了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)

2 个答案:

答案 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 ab,并返回c。第二个函数(a -> b) -> c接受一个参数a -> b并返回c

那么,你如何撰写flattenalmostAp?您无法使用正常的函数组合来执行此操作,因为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