You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为隐藏变量添加Monoid约束及ZipList的Applicative实例实现疑问

调整ZipList'的Applicative实例实现 & 隐藏变量的Monoid约束问题

最近在啃《Haskell Book》的Applicative章节,写自定义ZipList'的Applicative实例时,发现一开始的思路绕了弯路,现在重新梳理了实现方案;同时还碰到了给隐藏变量添加Monoid约束的相关疑问,先把原来的代码框架贴出来:

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')) = (...的内容)

调整后的Applicative实例实现

我一开始犯了个典型错误:把pure实现成只包含单个元素的List,但ZipList的核心语义是逐元素函数应用,如果pure只提供一个元素,那和任何更长的ZipList'结合时都会提前终止,完全不符合预期。

后来调整了思路,先实现一个生成无限重复元素的辅助函数,然后基于它来实现pure;(<*>)则处理两个List的逐元素配对应用,遇到空列表就停止:

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

-- 生成无限重复元素的自定义List
repeat' :: a -> List a
repeat' x = Cons x (repeat' x)

-- 解开ZipList'包装的辅助函数
getZipList' :: ZipList' a -> List a
getZipList' (ZipList' l) = l

instance Applicative ZipList' where
  -- pure生成无限重复当前元素的ZipList',符合ZipList的语义
  pure a = ZipList' (repeat' a)
  -- 空列表和任何列表结合都返回空
  ZipList' Nil <*> _ = ZipList' Nil
  _ <*> ZipList' Nil = ZipList' Nil
  -- 逐元素应用函数,递归处理剩余列表
  ZipList' (Cons f fs) <*> ZipList' (Cons x xs) =
    ZipList' $ Cons (f x) (getZipList' $ ZipList' fs <*> ZipList' xs)

这样调整后,pure出来的ZipList'可以和任意长度的ZipList'正常结合,比如pure (+1) <*> ZipList' (Cons 1 (Cons 2 Nil))会得到ZipList' (Cons 2 (Cons 3 Nil)),完全符合Applicative的法则。

关于隐藏变量的Monoid约束问题

一开始我纠结“给隐藏变量加Monoid约束”,后来想明白:如果只是实现Applicative实例,完全不需要Monoid约束,因为Applicative的核心操作和Monoid无关。

但如果是想给ZipList'添加Monoid实例,或者需要在某些场景下合并多个ZipList',这时候才需要考虑约束——不过约束不是针对隐藏的List类型,而是针对元素类型a。比如我们想实现“逐元素合并”的Monoid行为(而不是拼接List),就需要a是Monoid:

instance Monoid a => Monoid (ZipList' a) where
  -- mempty是无限重复元素的mempty
  mempty = ZipList' (repeat' mempty)
  -- mappend逐元素用Monoid的mappend合并,取较短列表的长度
  mappend (ZipList' xs) (ZipList' ys) = ZipList' (zipWithMappend xs ys)
    where zipWithMappend Nil _ = Nil
          zipWithMappend _ Nil = Nil
          zipWithMappend (Cons x xs') (Cons y ys') = Cons (x `mappend` y) (zipWithMappend xs' ys')

这里的约束Monoid a是为了让每个元素都能被合并,而不是给隐藏的List加约束——毕竟List本身的Monoid是拼接,但这不符合ZipList'的逐元素语义。

内容的提问来源于stack exchange,提问作者tesserakt

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 08:42:09