Skip to content
Development
Skill

/nw-fp-clojure

Clojure language-specific patterns, data-first modeling, REPL-driven development, and spec

From plugin
nwave
628151 skills34 agents27 commands

Claude Code

$ npx -y skills add nWave-ai/nWave --skill nw-fp-clojure --agent claude-code

SKILL.md

nw-fp-clojure.SKILL.md
name: nw-fp-clojure
agent: nw-functional-software-crafter
description: Clojure language-specific patterns, data-first modeling, REPL-driven development, and spec
user-invocable: false
disable-model-invocation: true

FP in Clojure -- Functional Software Crafter Skill

Cross-references: [fp-principles](../nw-fp-principles/SKILL.md) | [fp-domain-modeling](../nw-fp-domain-modeling/SKILL.md)

When to Choose Clojure

  • Best for: data-centric domains | REPL-driven exploration | rapid prototyping | frequently evolving data shapes
  • Not ideal for: teams wanting compile-time type safety | Android | teams unfamiliar with Lisp syntax

[STARTER] Quick Setup

# Install Clojure CLI
brew install clojure/tools/clojure  # macOS
# Create project
mkdir -p order-service/src/order_service order-service/test/order_service
# Run REPL: clj | Run tests: clj -X:test

**Namespace caveat**: Clojure namespaces use `-` but filenames use `_` (JVM requirement). `order-service.core` lives in `order_service/core.clj`.

[STARTER] Domain Modeling with Spec

Clojure is dynamically typed. Domain modeling uses maps with qualified keywords, validated at runtime.

(require '[clojure.spec.alpha :as s])

;; Domain wrappers as specs
(s/def ::order-id pos-int?)
(s/def ::email (s/and string? #(clojure.string/includes? % "@")))
(s/def ::customer-name (s/and string? #(> (count %) 0)))

;; Record types as spec'd maps
(s/def ::customer (s/keys :req [::customer-name ::email] :opt [::phone]))

;; Choice types as spec alternatives
(s/def ::payment-method
  (s/or :credit-card (s/keys :req [::card-number ::expiry-date])
        :bank-transfer (s/keys :req [::account-number])
        :cash #{:cash}))

[STARTER] Validated Construction

(defn make-email [raw-email]
  (if (s/valid? ::email raw-email)
    {:ok raw-email}
    {:error (str "Invalid email: " raw-email)}))

[INTERMEDIATE] Auto-Generated Test Data from Specs

(require '[clojure.spec.gen.alpha :as gen]
         '[clojure.spec.test.alpha :as stest])

(gen/sample (s/gen ::customer) 5)  ;; random valid customers

;; Auto-test functions against their specs
(s/fdef validate-order
  :args (s/cat :raw-order ::raw-order)
  :ret (s/or :ok ::validated-order :error ::validation-error))
(stest/check `validate-order)

Define specs, get generators and function tests for free.

[INTERMEDIATE] Composition Style

Threading Macros

;; Thread-first (data through first arg) / Thread-last (for collections)
(-> raw-order validate-order price-order confirm-order)
(->> orders (filter active?) (map :customer-name) (sort))

;; comp composes right-to-left; partial for partial application
(def process-order (comp confirm-order price-order validate-order))

Error-Track Pipeline (Convention-Based)

(defn bind-result [result f]
  (if (:ok result) (f (:ok result)) result))

(defn place-order [raw-order]
  (-> {:ok raw-order}
      (bind-result validate-order)
      (bind-result price-order)
      (bind-result confirm-order)))

No stdlib Result/Either -- by convention using `{:ok v}` / `{:error r}` maps.

[INTERMEDIATE] Effect Management

Side effects managed by convention and architecture, not the type system.

Pure Core / Imperative Shell

;; Pure domain logic (no I/O, no state)
(defn calculate-discount [order]
  (if (> (count (:lines order)) 10) {:rate 0.1} {:rate 0.0}))

;; Imperative shell (I/O at edges)
(defn place-order-handler! [deps raw-order]
  (let [result (-> raw-order
                   validate-order
                   (bind-result (partial price-order (:get-price deps)))
                   (bind-result confirm-order))]
    (when (:ok result)
      ((:save-order! deps) (:ok result)))
    result))

[ADVANCED] Hexagonal Architecture with Functions

;; Functions as dependencies (idiomatic Clojure)
(defn make-place-order-handler [find-order-fn save-order-fn!]
  (fn [raw-order]
    (let [result (validate-and-price raw-order)]
      (when (:ok result) (save-order-fn! (:ok result)))
      result)))

;; Composition root
(def handler
  (make-place-order-handler
    (partial find-order-in-db datasource)
    (partial save-order-in-db! datasource)))

For lifecycle management, use Component, Integrant, or Mount:

(require '[integrant.core :as ig])

(defmethod ig/init-key ::order-repo [_ {:keys [datasource]}]
  (->PostgresOrderRepo datasource))

[INTERMEDIATE] Testing

**Frameworks**: clojure.test (built-in) | test.check (PBT) | Kaocha (test runner).

Property Test with test.check

(require '[clojure.test.check.clojure-test :refer [defspec]]
         '[clojure.test.check.generators :as gen]
         '[clojure.test.check.properties :as prop])

(defspec serialization-round-trips 100
  (prop/for-all [order (s/gen ::order)]
    (= order (deserialize (serialize order)))))

Custom Generator

(def gen-valid-email
  (gen/fmap (fn [[user domain]] (str user "@" domain ".com"))
    (gen/tuple
      (gen/such-that not-empty (gen/string-alphanumeric))
      (gen/such-that not-empty (gen/string-alphanumeric)))))

[ADVANCED] Idiomatic Patterns

Data-First Domain Modeling

(def order
  {::order-id 42
   ::customer {::customer-name "Alice" ::email "alice@example.com"}
   ::lines [{::product-code "W-1234" ::quantity 10 ::price 25.0}]
   ::status :validated})

"Data is better than types." Domain models are maps. Validation happens at boundaries.

Multimethods for State Machines

(defmulti handle-command (fn [state _command] (:status state)))

(defmethod handle-command :empty [_state {:keys [item]}]
  {:status :active :lines [item]})

(defmethod handle-command :active [state {:keys [action] :as command}]
  (case action
    :add-item (update state :lines conj (:item command))
    :pay (assoc state :status :paid)
    state))

(defmethod handle-command :paid [state _command]
  state)
Read more
@nwave-ai
Ships withnwave

AI agents that guide you from idea to working code, with human judgment at every gate. nWave runs inside Claude Code. It breaks feature delivery into seven waves (discover, diverge, discuss, design, devops, distill, deliver).

628151 skills34 agents
Get the whole plugin

Stars on GitHub

Momentum

stars in total
628
Star history of nWave-ai/nWave on GitHub, a chart by star-history.com

Other skills on nwave.

nw-ad-critique-dimensions

nw-ad-critique-dimensi…

Review dimensions for acceptance test quality - happy path bias, GWT compliance, business language purity, coverage completeness,…

@nwave-ai@nwave-aiInstall Skill
nw-agent-testing

nw-agent-testing

5-layer testing approach for agent validation including adversarial testing, security validation, and prompt injection resistance

@nwave-ai@nwave-aiInstall Skill

More skills from other plugins.

brainstorming
Auto-invoked

brainstorming

You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…

@obra@obraInstall Skill
ask-matt

ask-matt

Ask which skill or flow fits your situation. A router over the skills in this repo.

skills10d309k
@mattpocock@mattpocockInstall Skill
accessibility
Auto-invoked

accessibility

Design, implement, and audit accessible UI to WCAG 2.2 Level AA across Web, iOS, and Android — semantic ARIA roles and labels,…

ecc12d293k
@affaan-m@affaan-mInstall Skill
compiler-commit

compiler-commit

Use when you want to verify compiler changes and commit with the correct convention. Runs tests, lint, and format, then commits…

react1d252k
@facebook@facebookInstall Skill

Questions about nw-fp-clojure.

Which coding agents can use nw-fp-clojure?
nw-fp-clojure installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.