Files
bara-lang/books/clojure-book/en/03-tooling.md
T
dimgigov 23252826a0 feat: add Clojure/Nim chapter to the book + restructure for GitLab
- New chapter 05-clojure-nim.md (EN + BG) covering:
  - Native compilation pipeline (Clojure → Nim → C → binary)
  - AI-powered development (error explanation, code generation)
  - JSON REPL for AI agents
  - loop/recur with real TCO
  - Cross-compilation: JS, shared libs, WASM
  - Persistent data structures (HAMT)
  - Concurrency: atoms, agents, channels
- Updated book README.md with Clojure/Nim focus
- Added Clojure/Nim terms to subject indices (EN + BG)
- Removed books/ from .gitignore so it can be pushed to GitLab
2026-05-08 23:32:11 +03:00

11 KiB

Pure Clojure: Project Structure and Tools

Table of Contents

  1. Project Structure
  2. deps.edn and CLI Tools
  3. Testing Infrastructure
  4. Development Workflow
  5. Code Quality Tools
  6. Building and Deployment
  7. Library Ecosystem
  8. Debugging Techniques
  9. Index

1. Project Structure

1.1 Typical Project Layout

myproject/
├── deps.edn
├── src/
│   └── myproject/
│       ├── core.clj
│       ├── util.clj
│       └── spec/
│           └── core_spec.clj
├── test/
│   └── myproject/
│       ├── core_test.clj
│       └── util_test.clj
├── resources/
├── doc/
│   └── intro.md
└── README.md

1.2 Namespaces as File Paths

Namespaces map to file paths:

;; src/myproject/core.clj
(ns myproject.core)

;; src/myproject/util.clj
(ns myproject.util)

;; src/myproject/spec/user.clj
(ns myproject.spec.user)

1.3 Multi-module Projects

;; deps.edn with multiple modules
{:paths ["src" "modules/common/src"]
 :deps {org.clojure/clojure {:mvn/version "1.11.1"}}
 :aliases
 {:dev {:extra-paths ["test"]
        :extra-deps {}}}}

1.4 Source vs Test Separation

  • Source code: src/ directory
  • Tests: test/ directory
  • Both added to classpath during development

2. deps.edn and CLI Tools

2.1 deps.edn Reference

{:deps {org.clojure/clojure {:mvn/version "1.11.1"}
        org.clojure/data.json {:mvn/version "2.4.0"}
        clojure.java-time/clojure.java-time {:mvn/version "1.4.2"}
        }
 :paths {:src ["src"]
         :test ["test"]}
 :aliases
 {:test {:extra-paths ["test"]
         :extra-deps {org.clojure/test.check {:mvn/version "1.1.1"}}}
  :dev {:jvm-opts ["-Xmx4g"]}
  :bench {:extra-deps {criterium/criterium {:mvn/version "0.4.4"}}}}

2.2 Running Code

# Run a script
clj -M script.clj

# Run with test alias
clj -M:test -m myproject.test-runner

# Run REPL with deps
clj -M

# Run with specific deps
clj -Sdeps '{:deps {org.clojure/clojure {:mvn/version "1.11.1"}}}'

2.3 Understanding Aliases

Aliases modify classpath or behavior:

# Test with generative checking
clj -M:test:gen

# Production build
clj -M:prod build

# Dev mode with extra checks
clj -M:dev

2.4 Making Dependencies

;; deps.edn
{:deps {io.github.clojure/data.json {:git/sha "..."}}}

;; Classpath
clj -Sdescribe  # Show effective classpath

3. Testing Infrastructure

3.1 test.check for Generative Testing

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

(def prop-sort-idempotent
  (prop/for-all
    [v (gen/vector gen/int)]
    (= (sort v) (sort (sort v)))))

(tc/quick-check 100 prop-sort-idempotent)
;; => {:result true, :pass? true, ...}

3.2 Fixtures for Setup/Teardown

(ns myproject.test-util
  (:require [clojure.test :as t]
            [myproject.db :as db]))

(defn with-test-db [f]
  (db/connect! :test)
  (f)
  (db/disconnect!))

(defn with-logging [f]
  (println "Before test")
  (f)
  (println "After test"))

;; Apply fixtures
(t/use-fixtures :each with-test-db with-logging)
(t/use-fixtures :once db/setup-once)

3.3 Test Namespaces

(ns myproject.core-test
  (:require [clojure.test :as t]
            [myproject.core :as core]))

(t/deftest ^:integration api-test
  "Integration tests for external API"
  ...)

;; Run specific tests
(t/run-tests 'myproject.core-test)
(t/run-tests #"myproject.*test")

3.4 Expectations-style Testing

;; Using expectations library
(require '[expectations :refer [expect]])

(expect 4 (+ 2 2))
(expect ArithmeticException (/ 1 0))
(expect [:a :b :c] (filterv odd? [1 2 3 4]))

3.5 Midje (for readablity)

(require '[midje.sweet :refer [fact facts =>]])

(fact "addition works"
  (+ 2 2) => 4)

(facts "about strings"
  (fact "upper-case works"
    (clojure.string/upper-case "hello") => "HELLO"))

4. Development Workflow

4.1 REPL-driven Development

;; Start REPL
user=> (require '[myproject.core :as core] :reload)

;; Edit code, reload
user=> (require '[myproject.core :as core] :reload :verbose)

;; Reload all changed namespaces
user=> (require '[clojure.tools.namespace.repl :as ns]
                 :refer [refresh refresh-all])
user=> (refresh)

4.2 Hot Loading Code

;; In REPL
(def server (atom nil))

(defn start-server []
  (swap! server assoc :running true))

;; After editing core, just reload
(require '[myproject.core :as core] :reload)

4.3 Source Tracking

;; Track which files changed
(require '[clojure.java.io :as io]
         '[clojure.tools.namespace.track :as track])

(defn track-changes []
  (let [tracker (atom (track/tracker))]
    (fn []
      (swap! tracker track/step)
      (track/deps @tracker))))

4.4 Rich Comment Blocks

(defn process-data [input]
  {:pre [(sequential? input)]}
  (mapv inc input))

(comment
  ;; Development experiments
  (process-data [1 2 3])

  ;; Test edge cases
  (process-data [])

  ;; Interactive debugging
  (def test-data (load-data))
  )

5. Code Quality Tools

5.1 eastwood (Linter)

;; deps.edn alias
:lint {:extra-deps {lancem对待/clojure-eastwood {:mvn/version "..."}}}

;; Run
clj -M:lint -m eastwood.lint

5.2 clj-kondo (Fast Linter)

# Install
brew install clj-kondo

# Run on project
clj-kondo --lint src/

5.3 Formatting with cljfmt

;; deps.edn
:format {:extra-deps {com.github.cljfmt/cljfmt {:mvn/version "..."}}}

;; Check
clj -M:format check src/

;; Fix
clj -M:format fix src/

5.4 Typed Clojure (Optional Type Checking)

(require '[clojure.core.typed :as t])

(t/ann my-function [t/Int -> t/Int])
(defn my-function [x]
  (inc x))

;; Check types
(t/check-ns)

6. Building and Deployment

6.1 Building Uberjars

;; tools.build
(require '[clojure.tools.build.api :as b])

(def lib 'com.mycompany/myapp)
(def version "1.0.0")
(def target-dir "target")

(defn uberjar [opts]
  (b/java-command
    {:basis (:basis opts)
     :main 'myproject.core
     :jar-file (b/path target-dir "myapp.jar")
     :uber-jar true}))

6.2 Native Image with GraalVM

# Compile Clojure to JVM bytecode first
clojure -M:build compile

# Build native image
native-image --initialize-at-build-time=clojure.lang.RT \
             -jar myapp.jar

6.3 Docker Integration

FROM clojure:openjdk-17-lein

WORKDIR /app
COPY deps.edn .
RUN clj -M -P

COPY src src
COPY test test

ENVlein uberjar
ENTRYPOINT ["java", "-jar", "target/myapp.jar"]

6.4 CI/CD Pipeline

# GitHub Actions example
name: Clojure CI

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: DeLaGuardo/setup-clojure@10
        with:
          backend: 'cli'
          version: '1.11.1'
      - run: clj -M:test
      - run: clj -M:lint

7. Library Ecosystem

7.1 Web Development

;; HTTP Server - Ring (pure Clojure)
{:deps {ring/ring {:mvn/version "1.9.5"}}

;; Routes - Compojure
{:deps {compojure/compojure {:mvn/version "1.6.2"}}}

7.2 Data Processing

;; Data manipulation
{:deps {zipkin/zikaron {:mvn/version "1.0.0"}}}

;; CSV handling
{:deps {org.clojure/data.csv {:mvn/version "1.0.1"}}

;; JSON (pure Clojure)
{:deps {org.clojure/data.json {:mvn/version "2.4.0"}}}

7.3 Async Programming

;; Core async is built-in
;; No additional dependencies for channels

;; For additional async patterns
{:deps {manifold/manifold {:mvn/version "0.4.0"}}}

7.4 Database Access

;; Pure Clojure JDBC wrapper
{:deps {org.clojure/java.jdbc {:mvn/version "0.7.12"}}

;; SQL DSL
{:deps {sqlkorma/sqlkorma {:mvn/version "0.4.0"}}}

7.5 Testing Libraries

{:deps {expectations/expectations {:mvn/version "2.0.0"}}
       {midje/midje {:mvn/version "1.9.10"}}
       {org.clojure/test.check {:mvn/version "1.1.1"}}}

7.6 Finding Libraries

  • Clojars.org - Community repository -.Clojuredocs.org - Documentation with examples
  • Awesome-clojure - Curated list

8. Debugging Techniques

8.1 Print Debugging

;; Simple prints
(println "Debug:" variable)

;; Withprympr
(require '[clojure.pprint :as pp])
(pp/pprint data-structure)

;; Tap for debugging
(tap> {:event :processing :data x})

8.2 Stack Traces

;; Get full stack trace
(.printStackTrace *e)

;; Ex-data for spec errors
(try
  (s/conform ::spec value)
  (catch Exception e
    (ex-data e)))

8.3 REPL Debugging

;; Inspect values
user> (def data (s/gen ::my-spec))
user> data

;; Step through with trace
(trace (reduce + (range 10)))

8.4 Watching State

;; Watch atoms
(add-watch my-atom :debug
           (fn [k r o n]
             (println "Changed:" o "->" n)))

;; Watch refs in transaction
(dosync
  (trace (alter my-ref f)))

8.5 Breakpoints

;; Using dbg macro (from tools.trace)
(require '[clojure.tools.trace :as t])
(t/dbg expression)

8.6 Logging

(require '[clojure.tools.logging :as log])

(log/info "Application started")
(log/debug "Processing" :item item)
(log/error e "Failed processing")

9. Index

A

B

C

  • check-ns - 5.4
  • clojure.tools.namespace - 4.1
  • comment - 4.4

D

E

  • eastwood - 5.1

F

  • find-libs - 7.6

G

I

  • instrument - 3.1

L

M

N

  • ns-publics - 4.1

P

  • pp/pprint - 8.1
  • profile - 8.3

Q

  • quick-check - 3.1

R

S

T

U

  • uberjar - 6.1
  • use-fixtures - 3.2

V


Pure Clojure: Project Structure and Tools