Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Clojure language-specific patterns, data-first modeling, REPL-driven development, and spec
.claude/skills/nwave-ai-nw-fp-clojure/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 38% | 0% |
| case-13 | ✗→✓ | ▲ Improved | 46% | 0% |
| case-17 | ✗→✓ | ▲ Improved | 20% | 0% |
| case-22 | ✗→✓ | ▲ Improved | 11% | 0% |
| case-23 | ✗→✓ | ▲ Improved | 21% | 0% |
Cross-references: fp-principles | fp-domain-modeling
bash# 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.
Clojure is dynamically typed. Domain modeling uses maps with qualified keywords, validated at runtime.
clojure(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}))
clojure(defn make-email [raw-email] (if (s/valid? ::email raw-email) {:ok raw-email} {:error (str "Invalid email: " raw-email)}))
clojure(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.
clojure;; 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))
clojure(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.
Side effects managed by convention and architecture, not the type system.
clojure;; 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))
clojure;; 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:
clojure(require '[integrant.core :as ig]) (defmethod ig/init-key ::order-repo [_ {:keys [datasource]}] (->PostgresOrderRepo datasource))
Frameworks: clojure.test (built-in) | test.check (PBT) | Kaocha (test runner).
clojure(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)))))
clojure(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)))))
clojure(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.
clojure(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)
nil, propagates silently. Use some-> and explicit nil checks at boundaries.s/merge.{:ok v} / {:error r} early.Other measured skills in the registry, with their headline benchmark lift.