将 Monoid 约束添加到隐藏变量

Adding a Monoid constraint to a hidden variable

我在 Applicative 的 Haskell 本书章节中。我正在为 ZipList 编写 Applicative 实例,我知道我想多了,正在改变我的方法。我的旧方法是:

data List a = Nil | Cons a (List a) deriving (Eq, Show)

newtype ZipList' a =
    ZipList' (List a)
    deriving (Eq, Show)

instance Applicative ZipList' where
    pure a = ZipList' (Cons a Nil)
    ZipList' Nil <*> _ = ZipList' Nil
    _ <*> ZipList' Nil = ZipList' Nil
    (ZipList' (Cons a bs)) <*> (ZipList' (Cons a' bs')) = (ZipList' (Cons (a(a')) Nil)) `mappend` (ZipList' bs <*> ZipList' bs')

我收到错误:

 No instance for (Monoid b) arising from a use of ‘mappend’
      Possible fix:
        add (Monoid b) to the context of
          the type signature for:
            (<*>) :: forall a b. ZipList' (a -> b) -> ZipList' a -> ZipList' b
    • In the expression:
        (ZipList' (Cons (a (a')) Nil))
          `mappend` (ZipList' bs <*> ZipList' bs')
      In an equation for ‘<*>’:
          (ZipList' (Cons a bs)) <*> (ZipList' (Cons a' bs'))
            = (ZipList' (Cons (a (a')) Nil))
                `mappend` (ZipList' bs <*> ZipList' bs')
      In the instance declaration for ‘Applicative ZipList'’

我认为这是因为 mappend 使用 Monoid 作为 ZipList 即:

instance Monoid a => Monoid (ZipList' a) where
    mempty = pure mempty
    mappend = liftA2 mappend

a 设置了 Monoid 约束。在 Applicative 实例中,我无法将 a 添加到 class 实例,除非它不再是正确的 Applicative 实例定义。我知道解决方案不正确,但它确实让我想到“我如何在 Applicative 实例中向 ZipList 的参数添加 Monoid 约束?

无需在此处附加任何内容。不要构建列表 Cons x Nil 并附加列表 y,只需构建 Cons x y.

let ZipList bs'' = ZipList' bs <*> ZipList' bs'
in ZipList' (Cons (a a') bs'')

关于 monoid-related 错误。这取决于您为您的类型使用的实例 ZipList'

如果你使用像

这样的东西
instance Monoid (ZipList' a) where
   mempty = ZipList' Nil
   mappend (ZipList' Nil) zs = zs
   mappend (ZipList' (Cons x xs)) zs = let
      ZipList' ys = mappend (Ziplist' xs) zs
      in ZipList' (Cons x ys)

那就不用Monoid a了。

我认为这完全取决于你想要什么样的幺半群。您提到的 Monoid (ZipList' a) 以逐点方式附加两个列表的每个组件,为此我们确实需要 Monoid a。但是,在应用程序实例中,您不需要那种追加,您需要列表连接。这可以使用上面的 monoid 实例来实现。

也就是说:您发布的实例,按点工作,可能是 ZipList 最自然的实例。但是没有什么能阻止你定义一个 non-instance 函数

appendZipList' :: ZipList' a -> ZipList' a -> ZipList' a

并在您的应用实例中使用它,而不依赖于幺半群。

否则,您可以为连接追加定义 instance Monoid (List a),并在应用实例中使用它

let ZipList' bs'' = ZipList' bs <*> ZipList' bs'
in ZipList' (Cons (a a') Nil `mappend` bs'')

我最终消除了 Monoid 要求,并在 Lists 上使用了一个函数,该函数遍历它们并将函数从左侧应用到右侧的值。

zipForMyList :: List (t -> a) -> List t -> List a
zipForMyList _ Nil                   = Nil
zipForMyList Nil _                   = Nil
zipForMyList (Cons f fs) (Cons a as) = (Cons (f a) (zipForMyList fs as))

repeatMyList :: p -> List p
repeatMyList x = xs where xs = Cons x xs

instance Applicative ZipList' where
    pure a = ZipList' (Cons a Nil)
    ZipList' Nil <*> _ = ZipList' Nil
    _ <*> ZipList' Nil = ZipList' Nil
    (ZipList' fs) <*> (ZipList' as) = ZipList' (zipForMyList fs as)