你所描述的本质上是一个幺半群。在 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 的总和定义为Nums的乘积定义为。mappend(+)Nummappend(*)
该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函数,因为它有一个依赖于memptyand的默认实现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。按照您认为合适的方式定义它。