简答:这是foldMap签名中的类型约束。
如果我们查看Foldable(更具体地说是foldMap)的源代码,我们会看到:
class Foldable (t :: * -> *) where
...
foldMap :: Monoid m => (a -> m) -> t a -> m
这意味着如果我们声明Tree 是Foldable 的成员(不是Tree 具有类型* -> *),这意味着在该树上定义了foldMap,例如:@987654331 @。所以这意味着结果类型(以及传递给foldMap的函数的结果)m必须是Monoid。
Haskell 是静态类型的:在编译之后,Haskell 准确地知道每个函数 instance 中传入的类型。这意味着它知道例如输出类型是什么,以及如何处理它。
现在Int 不是Monoid 的实例。你在这里使用F.foldl (+) 0 testTree,这意味着你或多或少地构建了一个“临时”幺半群。如果我们查看source code of foldl,则此方法有效:
foldl :: (b -> a -> b) -> b -> t a -> b
foldl f z t = appEndo (getDual (foldMap (Dual . Endo . flip f) t)) z
这有很多围绕参数f、z 和t 的逻辑。所以让我们先分解一下。
让我们先来看看Dual . Endo . flip f。简称:
helper = \x -> Dual (Endo (\y -> f y x))
Dual 和 Endo 是每个构造函数都接受一个参数的类型。所以我们将f y x 的结果包装在Dual (Endo ...) 构造函数中。
我们将把它用作foldMap 的函数。如果我们的f 的类型为a -> b -> a,那么这个函数的类型为b -> Dual (Endo a)。所以传递给foldMap的函数的输出类型为Dual (Endo a)。现在,如果我们检查源代码,我们会看到两件有趣的事情:
instance Monoid (Endo a) where
mempty = Endo id
Endo f `mappend` Endo g = Endo (f . g)
instance Monoid a => Monoid (Dual a) where
mempty = Dual mempty
Dual x `mappend` Dual y = Dual (y `mappend` x)
(注意是y `mappend` x,不是x `mappend` y)。
所以这里发生的是foldMap 中使用的mempty 是mempty = Dual mempty = Dual (Endo id)。所以一个封装了身份函数的Dual (Endo ...)。
此外,两个对偶的mappend 归结为Endo 中值的函数组合。所以:
mempty = Dual (Endo id)
mappend (Dual (Endo f)) (Dual (Endo g)) = Dual (Endo (g . f))
这意味着如果我们折叠树,如果我们看到Empty(一片叶子),我们将返回mempty,如果我们看到Node x l r,我们将执行@987654368 @ 如上所述。所以一个“specialized”foldMap 看起来像:
-- specialized foldMap
foldMap f Empty = Dual (Endo id)
foldMap f (Node x l r) = Dual (Endo (c . b . a))
where Dual (Endo a) = foldMap f l
Dual (Endo b) = helper x
Dual (Endo c) = foldMap f l
因此,对于每个Node,我们在节点的子节点和项上从右到左创建一个函数组合。 a 和 c 也可以是树的组合(因为它们是递归调用)。对于Leaf,我们什么都不做(我们返回id,但id 上的组合是无操作的)。
也就是说,如果我们有一棵树:
5
|- 3
| |- 1
| `- 6
`- 9
|- 8
`- 10
这将产生一个函数:
(Dual (Endo ( (\x -> f x 10) .
(\x -> f x 9) .
(\x -> f x 8) .
(\x -> f x 5) .
(\x -> f x 6) .
(\x -> f x 3) .
(\x -> f x 1)
)
)
)
(省略了身份,以使其更清晰)。这是getDual (foldMap (Dual . Endo . flip f)) 的结果。但是现在我们需要对这个结果进行后期处理。使用getDual,我们将内容包装在Dual 构造函数中。所以现在我们有:
Endo ( (\x -> f x 10) .
(\x -> f x 9) .
(\x -> f x 8) .
(\x -> f x 5) .
(\x -> f x 6) .
(\x -> f x 3) .
(\x -> f x 1)
)
通过appEndo,我们得到包裹在Endo中的函数,所以:
( (\x -> f x 10) .
(\x -> f x 9) .
(\x -> f x 8) .
(\x -> f x 5) .
(\x -> f x 6) .
(\x -> f x 3) .
(\x -> f x 1)
)
然后我们将其应用于z“初始”值。这意味着我们将处理以z(初始元素)开头的链,并像这样应用它:
f (f (f (f (f (f (f z 1) 3) 6) 5) 8) 9) 10
所以我们构建了某种 Monoid,其中 mappend 被 f 替换,mempty 作为无操作(身份函数)。