在 Haskell 中列出 monad 实例 - 为什么在绑定操作中使用 concat?

List instance of monad in Haskell - why use concat in bind-operation?

我在这里发现了一些问题:Redefining monad list instance。我目前正试图让我的头缠绕在单子上。但是我在这里需要一些帮助,我没有将列表的实例定义作为 monad。

这是我给 monad 的列表实例的定义:

    instance Monad [] where
    xs >>= f = concat $ map f xs
    return x = [x]
    fail _ = []

我不明白,为什么我需要在绑定函数中使用 concat。 这是我对 (>>=):

的定义
    (>>=) :: Monad m => m a -> (a -> m b) -> m b

所以我有一些 monadic 值 m a 和一个函数,取一个值 a 并产生一个 monadic 值 m b 作为参数。我从 m a 'feed' a 进入函数 (a -> m b) 并因此得到一个单子值 m b 作为结果。 用我自己的话说:绑定运算符 (>>=) 允许链接 monadic 函数(返回 monadic 值),其中较早函数的输出值是下一个函数的输入。对吗?

回到列表实例。 map f xsxs 中的每个值使用函数 f。所以 map (*2) [1,2,3] 结果是 [2,4,6]。这就是我想要的吗?我应该如何在这里使用 concatconcat的定义如下:

    concat :: [[a]] -> [a]

为什么我在 (>>=) 函数中得到列表列表?是不是因为 list 是 monad 并且我从该列表中获取每个值以将其提供给 fmap 只是获取单例输入?但是我该如何遍历整个列表呢? 'picking each value' 发生在哪里?如果 map 将整个列表 xs 作为输入(这就是我的理解)为什么我应该得到一个列表列表?

如果

x :: [a]
f :: a -> [b]

然后

map f :: [a] -> [[b]]
map f x :: [[b]]

所以,我们需要将后者压扁为[b]。这是由 concat.

完成的

请注意 f 是如何生成一个列表的,因此 map 将其变成了列表的列表。这很关键:如果 f 不是生成列表而是 f :: a->b,那么我们不需要 concat —— 我们甚至不需要 monad,因为仿函数提供 fmap=map :: (a->b) -> [a] -> [b] 就够了。

monad 相对于 functor 的额外好处主要在于让 f 产生一个 monadic 类型的值。