你所描述的本质上是一个幺半群。在 GHCI 中:
Prelude> :m + Data.Monoid
Prelude Data.Monoid> :info Monoid
class Monoid a where
mempty :: a
mappend :: a -> a -> a
mconcat :: [a] -> a
如您所见,幺半群具有三个相关的功能:
该mempty
函数有点像幺半群的恒等函数。例如,aNum
可以表现为一个幺半群,只需要两个操作:求和和乘积。对于总和mempty
定义为0
。对于一个产品mempty
定义为1
。
mempty `mappend` a = a
a `mappend` mempty = a
该mappend
功能类似于您的union
功能。例如 s 的总和定义为Num
s的乘积定义为。mappend
(+)
Num
mappend
(*)
该mconcat
功能类似于折叠。然而,由于幺半群的特性,我们是从左侧折叠、从右侧折叠还是从任意位置折叠都无关紧要。这是因为mappend
应该是关联的:
(a `mappend` b) `mappend` c = a `mappend` (b `mappend` c)
但是请注意,Haskell 不执行幺半群定律。因此,如果您将类型设为类型类的实例,Monoid
那么您有责任确保它满足幺半群定律。
union
在您的情况下,很难从其类型签名中理解其行为方式: a -> a -> a
. 当然,您不能使类型变量成为类型类的实例。这是不允许的。你需要更具体。实际上是做什么的union
?
举一个例子来说明如何使一个类型成为 monoid 类型类的一个实例:
newtype Sum a = Sum { getSum :: a }
instance Num a => Monoid (Sum a) where
mempty = 0
mappend = (+)
就是这样。我们不需要定义mconcat
函数,因为它有一个依赖于mempty
and的默认实现mappend
。因此,当我们定义mempty
并免费mappend
获得mconcat
时。
现在你可以使用Sum
如下:
getSum . mconcat $ map Sum [1..6]
这就是正在发生的事情:
- 您正在将
Sum
构造函数映射[1..6]
到 production [Sum 1, Sum 2, Sum 3, Sum 4, Sum 5, Sum 6]
。
- 您将结果列表提供给将
mconcat
其折叠为Sum 21
.
- 您使用
getSum
从Num
.Sum 21
但是请注意,默认实现mconcat
是foldr mappend mempty
(即它是正确的折叠)。对于大多数情况,默认实现就足够了。但是,在您的情况下,您可能希望覆盖默认实现:
foldParallel :: Monoid a => [a] -> a
foldParallel [] = mempty
foldParallel [a] = a
foldParallel xs = foldParallel left `mappend` foldParallel right
where size = length xs
index = (size + size `mod` 2) `div` 2
(left, right) = splitAt index xs
现在我们可以创建一个新的实例Monoid
如下:
data Something a = Something { getSomething :: a }
instance Monoid (Something a) where
mempty = unionEmpty
mappend = union
mconcat = foldParallel
我们使用它如下:
getSomething . mconcat $ map Something [1..6]
请注意,我定义mempty
为unionEmpty
. 我不知道该union
函数作用于什么类型的数据。因此我不知道mempty
应该定义什么。因此,我只是简单地称呼它unionEmpty
。按照您认为合适的方式定义它。