内部列表在输出中被视为外部列表,但 list 仍然是 list。
另一种说法是
foreach x in [[1,2],[3,4]]:
foreach y in x:
emit y
和
foreach x in [1,2,3,4]:
emit x
就发射的元素而言是“相同的”。
我发现带有排列子实体的类型演示在视觉上非常吸引人:
(>>=) :: m a -> (a -> m b) -> m b
[[1,2],[3,4]] :: [[Int]] -- actually, (Num a) => [[a]], but never mind that
\x -> x :: a -> a
(>>=) :: m a -> ( a -> m b) -> m b
(>>=) [[1,2],[3,4]] :: ( a -> m b) -> m b m a ~ [[Int]]
(>>=) [[1,2],[3,4]] :: ( a -> [b]) -> [b] m ~ []
(>>=) [[1,2],[3,4]] :: ([Int] -> [b]) -> [b] a ~ [Int]
(>>=) [[1,2],[3,4]] (\ x -> x ) :: [b] [b] ~ [Int]
(>>=) [[1,2],[3,4]] (\ x -> x ) :: [Int] b ~ Int
-- actually, (Num b) => b
这里,原来是\ x -> x :: (Num b) => [b] -> [b],而不仅仅是a -> a。
你看,当([Int] -> [b])与(\ x -> x)的类型匹配时,创建[Int] ~ [b]的等价,[Int]中的[]来自“内部列表”,@中的a 987654332@;并且[b] 中的[] 来自“外部列表”,m b 中的m;但是列表就是列表,如上所述。
这就是允许将两个列表级别拆分(“连接”)为一个的原因——“扁平化”一个列表,或者更一般地说,将一个 monad 的两个“级别”“连接”为一个。
查看它的另一种方法是使用特定列表版本扩展一元代码:
[[1,2],[3,4]] >>= \x -> x
=== concatMap id [[1,2],[3,4]] === concat [ x | x <- [[1,2],[3,4]]]
=== concat [id [1,2], id [3,4]] === [ y | x <- [[1,2],[3,4]], y <- x]
=== [1,2,3,4] === [1,2,3,4]
对于concatMap f 中的f,重要的是它是一个列表生成函数:f :: a -> [b]。
而concatMap id === concat :: [[a]] -> [a] 是一个完全合法的函数。是的,concat 是 join 用于列表单子:
ma >>= f === join (fmap f ma) -- or, for lists,
=== concat (map f ma)
=== concatMap f ma -- the definition that we used above