关于 monoids
monoids 是一项将幺半群概念应用于 Clojure 和 ClojureScript 代码的技能,帮助识别合并与折叠中的结合运算及单位元,也涵盖可并行化的情况。
主要功能
- 01识别结合运算与单位元
- 02将 Clojure 核心函数用作幺半群
- 03无需特殊分支处理空集合
- 04利用结合律进行并行聚合
- 05判断运算是否不满足幺半群条件
适用场景
- 01用可复用运算替代临时合并逻辑
- 02让折叠稳健处理空集合
- 03并行执行满足结合律的聚合
- 04建模组合后的权限、计数器或配置
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ bsene/ skills --skill monoids安装后在 Claude Code 中输入/monoids确认已加载。
详细介绍
幺半群
幺半群是一种极简结构:一组值 + 一个满足结合律的二元运算(combine)+ 一个单位元(empty)。除此之外别无其他。在业务代码中识别出这种结构,就能用一套通用且经过一次性测试的 API,替代临时编写的合并逻辑——后者是边界情况中常见的 bug 来源。
本技能涵盖 Clojure/ClojureScript。两个语言中的概念相同,只有惯用写法不同。TypeScript 惯用写法(接口 Monoid<T>、折叠、业务幺半群)现已移至 typescript 技能 → ../typescript/composition/SKILL.md(Monoids 章节);请将 TS 问题引导至该参考资料。
参考资料:Les monoïdes : une abstraction omniprésente (evryg)
何时应用此技能
要留意的信号不是“幺半群”这个词,而是请求或代码中出现以下任一模式:
- 为特定类型手动编写的
merge(a, b)、combine(a, b)或add(a, b)函数 - 对列表执行
reduce/fold,并使用任意初始值 - 聚合计算前有
if (list.length === 0) throw / return nil - 希望并行化聚合计算(Map-Reduce、Kafka 分区聚合等)
- 自然可以“相加”的业务建模:权限、计数器、日志、配置
如果出现这些模式之一,请主动提出幺半群结构,而不是等用户提起。
Clojure / ClojureScript
在 Clojure 中,这种抽象通常是隐式的:相比将 Monoid 接口具体化,语言更鼓励对已有结合律且具有自然单位元的函数使用 reduce。许多 core 函数(+、*、str、into、merge-with、concat、clojure.set/union)本身就是幺半群——它们的零参数元数直接编码了单位元。
接口(如需显式具体化)
(defrecord Monoid [empty combine])
(defn fold [{:keys [empty combine]} items]
(reduce combine empty items))
实际使用时,优先采用下面的 core 函数,而不是将其具体化——这更符合惯用写法,而且可以直接获得单位元。
基本幺半群
;; Somme — (+) => 0, le neutre est intégré à l'arité 0
(reduce + [1 2 3]) ;; => 6
;; Concaténation de string
(apply str ["a" "b" "c"]) ;; => "abc"
;; Concaténation de collections
(reduce into [] [[1 2] [3 4]]) ;; => [1 2 3 4]
;; Booléens
(every? true? [true true false]) ;; and — neutre true
(some true? [false false true]) ;; or — neutre false
;; Composition de fonctions — (comp) => identity
(comp inc #(* % 2))
结合律 → 并行化
(defn parallel-fold [f init coll chunks]
(->> (partition-all (/ (count coll) chunks) coll)
(pmap #(reduce f init %))
(reduce f init)))
(parallel-fold + 0 (range 1000000) 4)
pmap 会在可用核心上分发;f 满足结合律,因此分块分组不会影响结果——这与分布式幺半群折叠的逻辑相同。
业务建模
;; Panier — merge-with est LE combinateur idiomatique pour les maps
(merge-with + {:apple 2 :banana 1} {:apple 1 :cherry 3})
;; => {:apple 3, :banana 1, :cherry 3}
;; Permissions — clojure.set/union est associatif, #{} est le neutre
(require '[clojure.set :as set])
(apply set/union [#{:read} #{:write} #{:read :admin}])
;; => #{:read :write :admin}
;; Event log — simple concaténation, [] est le neutre
(apply concat [[:e1 :e2] [:e3] [:e4 :e5]])
;; => (:e1 :e2 :e3 :e4 :e5)
单位元 → 健壮性
可变参数函数原生编码了单位元——“集合为空”的情况永远不会导致失败,无需特殊分支:
(reduce + []) ;; => 0, pas d'exception
(apply str []) ;; => ""
(merge-with + {}) ;; => {}
这里的单位元通常已经是函数零参数元数的默认行为——这表明该函数的设计考虑了代数性质。
可组合性
;; 1. "Produit de monoïdes" — juxt applique plusieurs fonctions et combine leurs résultats
(defn count-and-sum [coll]
((juxt count (partial reduce +)) coll))
(count-and-sum [10 20 30]) ;; => [3 60]
;; 2. Fonctions X -> M — combiner ponctuellement deux fonctions vers un monoïde
(defn combine-fns [f g combine]
(fn [x] (combine (f x) (g x))))
;; 3. Map<K, monoïde> — merge-with généralise à n'importe quel combine
(merge-with into
{:errors [] :warnings [:w1]}
{:errors [:e1] :warnings [:w2]})
;; => {:errors [:e1], :warnings [:w1 :w2]}
;; Compteurs d'événements par type — pattern fréquent en event-driven
(defn count-events [events]
(reduce (fn [acc e] (merge-with + acc {(:type e) 1}))
{}
events))
Clojure 特有的陷阱
不带 init 的 (reduce f coll) 即使没有显式单位元也能运行——第一个元素会作为初始累加器。这很方便,但可能掩盖单位元实际上并不存在:如果该领域没有自然的单位元,那么它就是半群,而不是幺半群;此时并行化或处理空集合的风险会比表面看起来更高。
注意事项
- 如果运算实际上不满足结合律(例如减法、除法),不要强行套用这种抽象——先验证该定律再提出,快速举一个反例就够了。
- 如果该领域不存在自然的单位元,它可能不是幺半群,而是半群(满足结合律但没有单位元)——应指出这一点,而不是凭空编造一个单位元。
- 保持务实:目标是用通用 API 替代有 bug 的临时代码,而不是为了显得专业就强行使用函数式术语。在 Clojure 中,如果
merge-with/reduce配合 core 函数就足够了,就不要多此一举地建议具体化Monoid。
基准测试
场景:.benchmarks/scenarios/monoids-001-parallel-merge.md · 运行:2026-08-31 · 日志:.benchmarks/runs/2026-08-31/monoids-001-parallel-merge.json
| 模型 | 未启用 | 已启用 | 变化 |
|---|---|---|---|
| claude-opus-4-8 | 100% | 100% | +0% |
| claude-sonnet-4-6 | 83% | 100% | +17% |
| claude-haiku-4-5 | 100% | 100% | +0% |
有条件通过(运行于 2026-08-31)。变化幅度为零是预期结果:法语正文(
# Monoïdes)不会对英文提示词触发(审计警告)。Sonnet 提升 17 个百分点(83→100);opus/haiku 无变化。门槛依据.agents/skills/skill-optimizer/rules/release-gates.md。