这是使用Alternative
可以完成的非常简单的示例:
import Control.Applicative
import Data.Foldable
data Nested f a = Leaf a | Branch (Nested f (f a))
flatten :: (Foldable f, Alternative f) => Nested f a -> f a
flatten (Leaf x) = pure x
flatten (Branch b) = asum (flatten b)
现在让我们尝试使用 Monoid
来做同样的事情:
flattenMonoid :: (Foldable f, Applicative f) => Nested f a -> f a
flattenMonoid (Leaf x) = pure x
flattenMonoid (Branch b) = fold (flattenMonoid b)
当然,这段代码无法编译通过,因为在fold (flattenMonoid b)
中,我们需要知道展平操作产生的容器元素是Monoid
的实例。因此,让我们将其添加到上下文中:
flattenMonoid :: (Foldable f, Applicative f, Monoid (f a)) => Nested f a -> f a
flattenMonoid (Leaf x) = pure x
flattenMonoid (Branch b) = fold (flattenMonoid b)
啊,但现在我们有一个问题,因为我们无法满足递归调用的上下文要求
Monoid (f (f a))
。那么让我们将其添加到上下文中:
啊,但是现在我们面临一个问题,因为我们无法满足递归调用的上下文要求Monoid (f (f a))
。因此,让我们将其添加到上下文中:
flattenMonoid :: (Foldable f, Applicative f, Monoid (f a), Monoid (f (f a))) => Nested f a -> f a
flattenMonoid (Leaf x) = pure x
flattenMonoid (Branch b) = fold (flattenMonoid b)
那就更糟糕了,因为现在递归调用需要更多的东西,即 Monoid (f (f (f a)))
...
如果我们可以这样写就太好了:
flattenMonoid :: ((forall a. Monoid a => Monoid (f a)), Foldable f, Applicative f, Monoid (f a)) => Nested f a -> f a
甚至只是
flattenMonoid :: ((forall a. Monoid (f a)), Foldable f, Applicative f) => Nested f a -> f a
我们可以这样做:不用写forall a. Monoid (f a)
,而是写Alternative f
。(我们也可以编写一个表达第一个更容易满足约束条件的类型类。)
Monoid
做什么,尤其是第3点和第4点: “3. 如果我们想要使用无限多个不同的a
.MonadPlus m => ...
而不是不可能。 4. 如果我们不知道需要什么a
。MonadPlus m => ...
而不是不可能。”如果有关此事的任何疑问,请具体提出。 - leftaroundaboutMonoid(f A), Monoid(f B)
的约束条件,但又怎样呢?——看起来并不那么糟糕”或者“为什么不将那些无限多/未知的类型放入一个存在/GADT包装器中”。但是所有这些解决方法在一个大项目中并不实用。只需查找一些具有Alternative
上下文的库函数,并尝试使用Monoid
编写它们即可。 - leftaroundabout