我有一个类型如下的函数:
union :: a -> a -> a
并且 a
具有 additivity 属性 . 所以我们可以将 union
视为 (+)
的一个版本
比如,我们有 [a]
,并且想要执行并行 "folding"
,对于非并行折叠,我们只能这样做:
foldl1' union [a]
但是如何并行执行呢?我可以在 Num
值和 (+)
函数上演示问题 .
例如,我们有一个列表 [1,2,3,4,5,6]
和 (+)
并行我们应该拆分
[1,2,3] (+) [4,5,6]
[1,2] (+) [3] (+) [4,5] (+) [6]
([1] (+) [2]) (+) ([3] (+) [4]) (+) ([5] (+) [6])
然后我们想要并行执行每个 (+)
操作,并结合起来回答
[3] (+) [7] (+) [11] = 21
注意,由于 a
可加性,我们拆分列表或以任何顺序执行操作 .
有没有办法使用任何标准库?
2 回答
您需要将
union
概括为任何关联二元运算符⊕,使得(a⊕b)⊕c==a⊕(b⊕c) . 如果同时你甚至有一个相对于neutral中性的单位元素,你就有一个幺半群 .关联性的重要方面是你可以在列表中任意分组连续元素的块,并且它们可以按任何顺序排列,因为a⊕(b⊕(c⊕d))==(a⊕b)⊕(c⊕d ) - 每个括号可以并行计算;那么你需要“减少”所有括号的“总和”,并且你已经对map-reduce进行了排序 .
为了使这种并行化有意义,你需要比⊕更快的分块操作 - 否则,顺序执行is比分块更好 . 一个这样的情况是当你有一个随机访问"list" - 比如一个数组 . Data.Array.Repa有很多并行折叠功能 .
如果你正在考虑自己实现一个实践,你需要选择一个好的复杂功能⊕这样的好处将显示出来 .
例如:
在这里,我故意使用一个仅在本地称为
mappend
的关联操作,以表明它可以用于比幺半群更弱的概念 - 只有关联性对于并行性很重要;因为并行性只对非空列表有意义,所以不需要mempty
.总运行时间为8.78秒,而
在我的双核笔记本电脑上总运行时间为5.89秒 . 显然,在这台机器上尝试超过-N2没有意义 .
你所描述的基本上是一个幺半群 . 在GHCI中:
如你所见,monoid有三个相关的函数:
mempty
函数有点像幺半群的身份函数 . 例如,Num
可以表现为monoid apropos两个操作:sum和product . 总和mempty
定义为0
. 对于产品mempty
定义为1
.mappend
函数类似于union
函数 . 例如,Num
的总和mappend
被定义为(+)
,而对于Num
的产品,mappend
被定义为(*)
.mconcat
功能类似于折叠 . 然而,由于幺半群的特性,我们是从左侧折叠,从右侧折叠还是从任意位置折叠都无关紧要 . 这是因为mappend
应该是关联的:但请注意,Haskell并未强制执行幺半群定律 . 因此,如果您将类型设为
Monoid
类型类的实例,那么您有责任确保它满足幺半群定律 .在您的情况下,很难理解
union
从其类型签名的行为:a -> a -> a
. 当然你不能允许't make a type variable an instance of a typeclass. That' . 你需要更具体 .union
究竟做了什么?为您举例说明如何使类型成为monoid类型类的实例:
那个's it. We don't需要定义
mconcat
函数,因为它有一个依赖mempty
和mappend
的默认实现 . 因此,当我们定义mempty
和mappend
时,我们免费得到mconcat
.现在您可以使用
Sum
,如下所示:这就是发生的事情:
您将
Sum
构造函数映射到[1..6]
以生成[Sum 1, Sum 2, Sum 3, Sum 4, Sum 5, Sum 6]
.您将结果列表提供给
mconcat
,将其折叠为Sum 21
.您使用
getSum
从Sum 21
中提取Num
.但请注意,
mconcat
的默认实现是foldr mappend mempty
(即它是右折叠) . 对于大多数情况,默认实现就足够了 . 但是在您的情况下,您可能希望覆盖默认实现:现在我们可以创建一个
Monoid
的新实例,如下所示:我们用它如下:
请注意,我将
mempty
定义为unionEmpty
. 我不知道union
函数的作用类型是什么 . 因此我不知道应该将mempty
定义为什么 . 因此,我只是称之为unionEmpty
. 根据需要定义它 .