/nw-fp-clojure
Clojure language-specific patterns, data-first modeling, REPL-driven development, and spec
Claude Code
$ npx -y skills add nWave-ai/nWave --skill nw-fp-clojure --agent claude-code
Codex
$ npx skills add nWave-ai/nWave --skill nw-fp-clojure -a codex -y
Cursor
$ npx skills add nWave-ai/nWave --skill nw-fp-clojure -a cursor -y
Gemini CLI
$ npx skills add nWave-ai/nWave --skill nw-fp-clojure -a gemini-cli -y
GitHub Copilot
$ npx skills add nWave-ai/nWave --skill nw-fp-clojure -a github-copilot -y
OpenCode
$ npx skills add nWave-ai/nWave --skill nw-fp-clojure -a opencode -y
Windsurf
$ npx skills add nWave-ai/nWave --skill nw-fp-clojure -a windsurf -y
SKILL.md
nw-fp-clojure.SKILL.mdname: 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
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)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).
Other skills on nwave.
More skills from other plugins.
brainstorming
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…
ask-matt
Ask which skill or flow fits your situation. A router over the skills in this repo.
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.

