bsene/skills

monoids

Recognize and apply the monoid abstraction (binary associative operation + neutral element) in Clojure/ClojureScript code.

Voir la source
Document Skill original

Rendu depuis le dépôt source en conservant titres, exemples, code, tableaux, liens et images.

Monoïdes

Un monoïde est une structure minimaliste : un ensemble de valeurs + une opération binaire associative (combine) + un élément neutre (empty). Rien de plus. Reconnaître cette structure dans du code métier permet de remplacer une logique de fusion ad hoc — source fréquente de bugs sur les cas limites — par une API générique et testée une fois pour toutes.

Cette skill couvre Clojure/ClojureScript. Le concept est identique dans les deux langages ; seule l'idiomatique change. L'idiomatique TypeScript (interface Monoid<T>, folds, monoïdes métier) vit désormais dans le skill typescript → ../typescript/composition/SKILL.md (section Monoids) ; routez les questions TS vers cette référence.

Source de référence : Les monoïdes : une abstraction omniprésente (evryg)

Quand appliquer cette skill

Le signal à repérer, ce n'est pas le mot "monoïde" — c'est un des patterns suivants dans la demande ou le code :

  • Une fonction merge(a, b), combine(a, b) ou add(a, b) codée à la main pour un type précis
  • Un reduce/fold sur une liste avec une valeur initiale arbitraire
  • Un if (list.length === 0) throw / return nil avant un calcul d'agrégat
  • L'envie de paralléliser un calcul d'agrégation (Map-Reduce, agrégation par partition Kafka, etc.)
  • Une modélisation métier qui "s'additionne" naturellement : permissions, compteurs, logs, config

Si un de ces patterns apparaît, propose activement la structure monoïdale plutôt que d'attendre que l'utilisateur la nomme.


Clojure / ClojureScript

En Clojure, l'abstraction est souvent implicite : le langage encourage reduce sur des fonctions déjà associatives avec un neutre naturel, plutôt que de réifier une interface Monoid. Beaucoup de fonctions core (+, *, str, into, merge-with, concat, clojure.set/union) sont déjà le monoïde — leur arité 0 encode directement le neutre.

L'interface (si besoin de la réifier explicitement)

clojure
(defrecord Monoid [empty combine])

(defn fold [{:keys [empty combine]} items]
  (reduce combine empty items))

En pratique, préfère les fonctions core ci-dessous plutôt que réifier — c'est plus idiomatique et le neutre vient gratuitement.

Monoïdes de base

clojure
;; 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))

Associativité → parallélisation

clojure
(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 distribue sur les cœurs disponibles ; l'associativité de f garantit que le regroupement en chunks n'affecte pas le résultat — c'est la même logique que le fold monoïdal distribué.

Modélisation métier

clojure
;; 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)

Élément neutre → robustesse

Les fonctions variadiques encodent le neutre nativement — le cas "collection vide" ne casse jamais, sans branche spéciale :

clojure
(reduce + [])       ;; => 0, pas d'exception
(apply str [])       ;; => ""
(merge-with + {})    ;; => {}

Ici le neutre est souvent déjà le comportement par défaut de la fonction à arité 0 — un signal que la fonction a été conçue avec l'algèbre en tête.

Composabilité

clojure
;; 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))

Piège spécifique à Clojure

(reduce f coll) sans init fonctionne même sans neutre explicite — le premier élément sert d'accumulateur initial. C'est pratique mais ça peut masquer l'absence réelle de neutre : si le domaine n'en a pas naturellement, c'est un semigroupe, pas un monoïde, et paralléliser ou traiter le cas vide devient plus risqué qu'il n'y paraît.


Garde-fous

  • Ne force pas l'abstraction si l'opération n'est pas vraiment associative (ex : soustraction, division) — vérifie la loi avant de la proposer, un contre-exemple rapide suffit.
  • S'il n'existe pas de neutre naturel dans le domaine, ce n'est peut-être pas un monoïde mais un semigroupe (associatif sans neutre) — le signaler plutôt que d'inventer une valeur neutre artificielle.
  • Reste pragmatique : l'objectif est de remplacer du code ad hoc buggé par une API générique, pas d'imposer du vocabulaire fonctionnel pour faire savant. En Clojure, si merge-with/reduce avec une fonction core suffit, ne propose pas de réifier un Monoid inutilement.

Benchmark

Scenario: .benchmarks/scenarios/monoids-001-parallel-merge.md · Run: 2026-08-31 · Log: .benchmarks/runs/2026-08-31/monoids-001-parallel-merge.json

ModelWithoutWithDelta
claude-opus-4-8100%100%+0%
claude-sonnet-4-683%100%+17%
claude-haiku-4-5100%100%+0%
SOFT PASS (run 2026-08-31). Flat deltas are the expected finding: the French-language body (# Monoïdes) does not activate for English-prompted runs (audit Warning). Sonnet +17 (83→100); opus/haiku unchanged. Gate per .agents/skills/skill-optimizer/rules/release-gates.md.
du même dépôt

Autres Skills

Tous les Skills
bsene
Communauté

c4-diagram

Generates C4 model architecture diagrams using Structurizr DSL (primary) or Mermaid C4 (fallback). Use when: user asks to "draw a diagram", "create a C4 diagram", "show architecture as a diagram", "generate architecture diagram", "document the system". DO NOT USE for code explanation or walkthroughs (use explain-code), class/sequence/ER diagrams (C4 is system-architecture only), or when a diagram is incidental to a code change — only when a C4 diagram is the primary deliverable.

installations
1
GitHub Stars
5
Mis à jour
21 sept.
bsene
Communauté

clean-code

Use when writing new code, naming modules/files/functions/classes/variables, reviewing or refactoring code for readability, assessing/reducing complexity, deciding whether an abstraction belongs, or reviewing with CUPID. For behavior-preserving refactors within a function, class, or file, route to the refactoring/ sub-skill. Trigger on requests like "name this function", "is this a good variable name", "review this for clean code", "reduce complexity", "should I add a comment here", "should I abstract this", "review with CUPID", "code smell", "extract method", or "refactor this class". Covers human-scale design, naming, complexity budgets, comments, CUPID, and in-place refactoring. Pairs with language-specific skills (typescript, go, clojurescript) for syntax/idiom concerns.

installations
1
GitHub Stars
5
Mis à jour
21 sept.
bsene
Communauté

clojurescript

Write, review, debug, and configure ClojureScript code and projects. Use this whenever the user mentions ClojureScript, .cljs/.cljc files, shadow-cljs, figwheel, the CLJS compiler, JS interop from Clojure, Reagent/re-frame/Reagent-style UI code, or asks to convert JS/TS logic into ClojureScript. Also use it for questions about CLJS compiler options (:optimizations, :main, :npm-deps, :externs, etc.), consuming JS/npm libraries from CLJS, source maps, Google Closure Library usage, or the newer ^:async/await function support. Also trigger for CLJS recursion/stack-overflow/trampoline questions. Trigger even if the user just pastes CLJS code with an error and asks "what's wrong here" or asks to set up a new CLJS project. Do NOT use for nbb (babashka/nbb) scripts, nbb.edn projects, or anything meant to run via nbb script.cljs/npx nbb — those have a different (SCI-interpreted, no-Closure-Compiler) language surface; those are out of scope for this skill — consult nbb's own documentation.

installations
1
GitHub Stars
5
Mis à jour
21 sept.
bsene
Communauté

communication

Analyze a real communication artifact (Slack/Teams message, email, meeting transcript, pitch or presentation draft, CV, or resume) and coach it sharper using seven rhetoric + structure techniques: ethos/logos/pathos, reframing tough questions, centering the other person, the Pyramid Principle, pattern interrupt, making ideas feel safe, and the cognitive-load through-line. Diagnose what works, what's missing, then rewrite. TRIGGER when: user wants to improve communication, "review my email/Slack/CV", "analyze this transcript", prep for a meeting/pitch/presentation, "how do I say this", be more persuasive/assertive, handle a tough question, talk to execs, "is this too long-winded". Also trigger for author style presets under references/authors/: DHH (blunt/direct), Uncle Bob (defend a quality/testing standard), Jessitron (reflective systems-thinking essay), Martin Fowler (measured mixed-feelings stance). DO NOT USE for raw text generation with no analysis, marketing/copywriting, translation, or code/docs.

installations
1
GitHub Stars
5
Mis à jour
21 sept.