Cheatsheet Clojure
Lisp moderno na JVM: funcional, imutável e concorrente
Clojure
Basic
Forms and S-expressions
; Everything is a list in prefix notation: (+ 1 2 3) ; 6 (* 2 3 4) ; 24 (str "Hello" " " "World") ; "Hello World" ; The first position is the function: (- 10 3) ; 7 (/ 22 7) ; 22/7 (ratio!)
Clojure uses S-expressions: code inside parentheses in prefix notation, where the first element is the function and the rest are the arguments.
cond and case
; cond: multiple conditions (cond (neg? x) "negative" (zero? x) "zero" :else "positive") ; case: constant value (fast) (case day 1 "monday" 2 "tuesday" "other") ; default
cond tests several predicates in sequence, with :else the the default. case compares against constant values and is faster, but requires a default branch.
Keywords and symbols
; Keywords (self-evaluating, used the keys):
:name
:age
(:name {:name "Anna"}) ; "Anna"
; Namespaced keywords:
::local ; :my.ns/local
:user/name
; Symbols (refer to vars):
'my-function
+ ; the symbol of the + function
(quote x) ; equivalent to 'xA keyword (starts with :) evaluates to itself and is widely used the a map key. A symbol refers to a var; quote (') prevents its evaluation.
Data Types
42 ; integer (Long) 3.14 ; float (Double) 22/7 ; ratio (exact rational!) "text" ; string \a ; char true false ; boolean nil ; null :keyword ; keyword 'symbol ; symbol
The main types include Long, Double, Ratio (exact rationals), String, Char, Boolean, nil, keywords and symbols. They are all immutable.
Arithmetic Operations
(+ 1 2) ; 3 (- 10 3) ; 7 (* 4 5) ; 20 (/ 22 7) ; 22/7 (ratio) (double (/ 22 7)) ; 3.142... (mod 10 3) ; 1 (remainder) (inc 5) ; 6 (dec 5) ; 4 (quot 10 3) ; 3 (quotient)
The operators are prefix functions: + - * /. Integer division returns an exact Ratio; use double to get a decimal. inc/dec add/subtract 1.
do and comments
; do runs several forms in sequence: (do (println "step 1") (println "step 2") (+ 1 2)) ; returns 3 (last form) ; Comments: ; this is a line comment #_ (form ignored by the reader) (+ 1 #_ 2 3) ; 4 (the 2 is ignored)
do runs several expressions and returns the value of the last one. Comments use ;; the #_ macro makes the reader completely ignore the next form.
def and let
; def creates a global binding (var):
(def name "Clojure")
(def pi 3.14159)
; let creates local bindings:
(let [x 10
y 20]
(+ x y)) ; 30
; let with reuse:
(let [base 5
twice (* base 2)]
(+ base twice)) ; 15def defines a global variable (a var). let creates local bindings visible only inside its body — it is the preferred form for temporary values.
Comparison and Logical
; Comparison (chainable!): (= 1 1) ; true (< 1 2 3) ; true (1<2<3) (>= 5 5 3) ; true ; Logical: (and true true) ; true (or false true) ; true (not true) ; false ; Useful predicates: (nil? nil) ; true (even? 4) ; true
Comparisons (= < > <= >=) accept several chained arguments. The logical ones are and, or and not. Functions ending in ? are predicates that return booleans.
if and when
; if: (if test then else) (if (> x 0) "positive" "negative or zero") ; when: only the true branch (when (pos? x) (println "It's positive!")) ; if returns a value (it is an expression): (def sign (if (neg? x) -1 1))
if is an expression that returns a value: (if test then else). when runs the body only if the test is true and returns nil otherwise.
Strings
; Concatenate: (str "Hello" " " "World") ; "Hello World" ; clojure.string library: (require '[clojure.string :the str]) (str/upper-case "hello") ; "HELLO" (str/lower-case "HELLO") ; "hello" (str/trim " hi ") ; "hi" (str/split "a,b,c" #",") ; ["a" "b" "c"] (str/join "-" [1 2 3]) ; "1-2-3"
The str function concatenates and converts values into text. The clojure.string namespace offers operations like upper-case, trim, split and join.
Functions
defn
; Define a function: (defn add [a b] (+ a b)) (add 3 5) ; 8 ; With docstring: (defn greeting "Returns a greeting" [name] (str "Hello, " name "!")) (greeting "Anna") ; "Hello, Anna!"
defn defines a named function. The optional docstring documents it and is visible with (doc greeting) in the REPL.
Shorthand #()
; #() is sugar for fn: #(* % 2) ; (fn [x] (* x 2)) #(+ %1 %2) ; two arguments #(apply + %&) ; variadic arguments ; Practical use: (map #(* % 2) [1 2 3]) ; (2 4 6) (filter #(> % 3) [1 2 3 4 5]) ; (4 5) ; Warning: do not nest #()!
The #() syntax is a short form of anonymous function. % is the argument, %1/%2 for several and %& for the rest. It should not be nested.
Threading macros
; -> inserts the the 1st argument:
(-> " hello "
(clojure.string/trim)
(clojure.string/upper-case)) ; "HELLO"
; ->> inserts the the last argument:
(->> (range 10)
(filter even?)
(map #(* % %))
(reduce +)) ; 120
; some-> for nil-safe:
(some-> obj :field :subfield)Threading macros avoid nested parentheses. -> passes the result the the first argument of each form; ->> the the last. some-> stops if it hits nil.
Arity overloading
; Multiple arities (number of args):
(defn area
([r] (* Math/PI r r)) ; circle
([l a] (* l a))) ; rectangle
(area 2) ; 12.56...
(area 3 4) ; 12
; Arity with recursion:
(defn sum-up-to
([n] (sum-up-to n 0))
([n acc]
(if (zero? n) acc
(recur (dec n) (+ acc n)))))A function can have several arities (numbers of arguments), each with its own body. It is common for a short arity to delegate to a more complete one with an initial value.
partial and comp
; partial: applies initial arguments (def twice (partial * 2)) (twice 5) ; 10 (def greet (partial str "Hello, ")) (greet "Anna") ; "Hello, Anna" ; comp: composes functions (right->left) (def transform (comp inc #(* % 2))) (transform 3) ; 7 (inc (2*3))
partial fixes the first arguments of a function, creating a new one. comp composes functions from right to left: (comp f g) is f(g(x)).
Variadic Functions
; & collects the rest of the arguments: (defn sum-all [& nums] (apply + nums)) (sum-all 1 2 3 4) ; 10 ; Mixing fixed and variadic: (defn log [level & msgs] (println level (apply str msgs))) (log "INFO" "a" "b") ; INFO ab
& collects the remaining arguments into a sequence. It allows functions with a variable number of arguments, like sum-all which accepts any amount.
apply
; apply calls a function with a seq of args: (apply + [1 2 3]) ; 6 (apply max [3 7 2]) ; 7 ; Fixed args + collection: (apply str "a" ["b" "c"]) ; "abc" ; Without apply vs with apply: (+ [1 2 3]) ; ERROR (apply + [1 2 3]) ; 6
apply calls a function passing the elements of a collection the individual arguments. It is essential for using functions like + or max with a list.
Anonymous Functions (fn)
; fn creates an anonymous function: (fn [x] (* x 2)) ; Direct use: ((fn [x] (* x 2)) 5) ; 10 ; As an argument: (map (fn [x] (* x x)) [1 2 3]) ; (1 4 9) ; With a local name (recursion): ((fn fact [n] (if (<= n 1) 1 (* n (fact (dec n))))) 5)
fn creates an anonymous function. It can have an internal name (for recursion) and is useful when the function is used only once, for example in map or filter.
loop and recur
; loop/recur: recursion with tail-call
(defn factorial [n]
(loop [i n
acc 1]
(if (<= i 1)
acc
(recur (dec i) (* acc i)))))
(factorial 10) ; 3628800loop defines recursion points and recur jumps back without growing the stack (tail-call optimization). It is the idiomatic way to do loops in Clojure.
Collections
Vectors
; Vector: ordered, index access (def v [1 2 3 4 5]) (nth v 0) ; 1 (v 2) ; 3 (the a function) (count v) ; 5 (conj v 6) ; [1 2 3 4 5 6] (assoc v 0 99) ; [99 2 3 4 5] (get v 10 :default) ; :default
A vector ([]) is an ordered collection with efficient index access. conj appends at the end and assoc replaces a position, always returning a new vector.
Sets
; Set: collection without duplicates
(def s #{1 2 3 4 5})
(contains? s 3) ; true
(s 3) ; 3 (or nil)
(conj s 6) ; #{1 2 3 4 5 6}
(disj s 3) ; #{1 2 4 5}
; Operations (clojure.set):
(require '[clojure.set :the set])
(set/union #{1 2} #{2 3}) ; #{1 2 3}
(set/intersection #{1 2} #{2 3}) ; #{2}A set (#{}) stores unique values with no order. conj adds, disj removes and contains? tests membership. The clojure.set namespace gives union and intersection.
Collections and sequences
; seq turns any collection into a sequence:
(seq [1 2 3]) ; (1 2 3)
(seq {:a 1}) ; ([:a 1])
(seq "abc") ; (\a \b \c)
; Test empty (idiomatic):
(if (seq coll)
"has data"
"empty")
; (empty? coll) also worksAny collection can be viewed the a seq (sequence) with seq. It is idiomatic to test (seq coll) to check if it has data, since it returns nil if empty.
Lists
; List: efficient access at the start (def l '(1 2 3)) (first l) ; 1 (rest l) ; (2 3) (cons 0 l) ; (0 1 2 3) (conj l 0) ; (0 1 2 3) (at the start!) ; Lists are lazy-friendly: (map inc l) ; (2 3 4)
A list ('()) is optimized for accessing and adding at the start. conj adds to the front (unlike the vector). They are ideal for lazy sequences.
Vector destructuring
; Extract positions: (let [[a b c] [1 2 3]] (+ a b c)) ; 6 ; First and rest: (let [[x & more] [1 2 3 4]] [x more]) ; [1 (2 3 4)] ; Ignore positions: (let [[_ second _] [10 20 30]] second) ; 20
Vector destructuring extracts values by position inside a let or parameter. & collects the rest and _ ignores unused positions.
Maps
; Map: key-value pairs
(def person {:name "Anna" :age 30})
; Access:
(:name person) ; "Anna"
(get person :age) ; 30
(get person :email "N/A") ; "N/A"
; keywords the a function:
(:age person) ; 30A map ({}) stores key-value pairs. keywords work the access functions: (:name person). get accepts a default value if the key does not exist.
Map destructuring
; :keys extracts keyword keys:
(let [{:keys [name age]} person]
(str name " - " age))
; With default and rename:
(let [{:keys [name]
:or {name "Anonymous"}} data]
name)
; In function parameters:
(defn show [{:keys [name age]}]
(println name age))Map destructuring with :keys extracts values from keyword keys into local variables. :or defines defaults and also works in function parameters.
Update maps
(def person {:name "Anna" :age 30})
; assoc: add/replace key
(assoc person :email "ana@mail.com")
; update: apply function to the value
(update person :age inc) ; age 31
; dissoc: remove key
(dissoc person :age)
; merge: combine maps
(merge person {:city "Lisbon"})Maps are immutable: assoc adds or replaces, update applies a function to the value of a key, dissoc removes and merge combines. They all return a new map.
Nested structures
(def data
{:user {:name "Anna"
:address {:city "Porto"}}})
; get-in: access a nested path
(get-in data [:user :address :city])
; "Porto"
; assoc-in: update a path
(assoc-in data [:user :age] 30)
; update-in: apply a function on the path
(update-in data [:user :name] clojure.string/upper-case)For nested structures, get-in reads a path of keys, assoc-in sets a value at that path and update-in applies a function to it — all immutably.
Sequences
map, filter, reduce
; map: transforms each element (map inc [1 2 3]) ; (2 3 4) (map #(* % %) [1 2 3]) ; (1 4 9) ; filter: keeps those that pass (filter even? (range 10)) ; (0 2 4 6 8) ; reduce: aggregates into a value (reduce + [1 2 3 4]) ; 10 (reduce + 100 [1 2 3]) ; 106 (initial value)
map applies a function to each element, filter keeps those that satisfy the predicate and reduce combines everything into a single value with an accumulator.
for comprehension
; Combines several generators:
(for [x (range 3)
y (range 3)]
[x y])
; With :when filter:
(for [x (range 10)
:when (even? x)]
(* x x)) ; (0 4 16 36 64)
; With :let binding:
(for [x (range 5)
:let [y (* x 2)]]
[x y])for is a list comprehension that combines generators. :when filters elements and :let creates intermediate bindings. It returns a lazy sequence.
some, every?, not-any?
; some: first truthy value
(some even? [1 3 4 5]) ; true
(some #{3} [1 2 3]) ; 3
; every?: do all pass?
(every? pos? [1 2 3]) ; true
(every? even? [2 4 5]) ; false
; not-any?: does none pass?
(not-any? neg? [1 2 3]) ; true
; not-empty: nil if empty
(not-empty []) ; nilsome returns the first truthy value of the predicate (or nil). every? tests whether all pass and not-any? whether none pass. They are sequence predicates.
take, drop, first, rest
(take 3 [1 2 3 4 5]) ; (1 2 3) (drop 2 [1 2 3 4 5]) ; (3 4 5) (take-while pos? [3 2 1 0 -1]) ; (3 2 1) (drop-while pos? [3 2 1 0 -1]) ; (0 -1) (first [1 2 3]) ; 1 (last [1 2 3]) ; 3 (rest [1 2 3]) ; (2 3) (nth [1 2 3] 1) ; 2
These functions slice sequences: take/drop take out N elements, take-while/drop-while use a predicate. first, last and rest access parts.
Transducers
; Compose transformations without intermediate collections:
(def xform
(comp
(filter even?)
(map #(* % %))
(take 5)))
; Apply in various ways:
(into [] xform (range 100)) ; [0 4 16 36 64]
(transduce xform + (range 100)) ; 56
(sequence xform (range 100))A transducer is a reusable transformation independent of the collection. Composed with comp, it is applied with into, transduce or sequence without creating intermediate collections.
Lazy sequences
; range is lazy (evaluated on demand):
(def nums (range)) ; infinite!
(take 5 nums) ; (0 1 2 3 4)
; lazy-seq creates a lazy seq:
(defn fib-seq
([] (fib-seq 0 1))
([a b]
(lazy-seq
(cons a (fib-seq b (+ a b))))))
(take 10 (fib-seq))
; (0 1 1 2 3 5 8 13 21 34)lazy sequences only compute their elements when needed. lazy-seq creates lazy sequences, allowing infinite structures consumed with take.
concat, interleave, partition
; concat: joins sequences (concat [1 2] [3 4]) ; (1 2 3 4) ; interleave: alternates elements (interleave [:a :b :c] [1 2 3]) ; (:a 1 :b 2 :c 3) ; partition: groups into blocks (partition 2 [1 2 3 4 5]) ; ((1 2) (3 4)) (partition-all 2 [1 2 3 4 5]) ; ((1 2) (3 4) (5)) ; flatten: removes nesting (flatten [[1 2] [3 [4]]]) ; (1 2 3 4)
concat joins sequences, interleave alternates elements from several and partition groups into blocks of N. flatten removes all levels of nesting.
range, repeat, cycle
; range: numeric sequence
(range 5) ; (0 1 2 3 4)
(range 2 10) ; (2 3 ... 9)
(range 0 10 2) ; (0 2 4 6 8) (step)
; repeat: repeats a value
(take 4 (repeat "x")) ; ("x" "x" "x" "x")
; cycle: repeats the collection
(take 6 (cycle [1 2 3])) ; (1 2 3 1 2 3)
; repeatedly: calls a function
(take 3 (repeatedly rand))range generates numeric sequences (with start, end and step). repeat repeats a value, cycle repeats a collection and repeatedly calls a function several times.
sort, group-by, frequencies
; sort / sort-by:
(sort [3 1 2]) ; (1 2 3)
(sort-by :age people) ; sorts by key
; group-by: groups by function
(group-by even? [1 2 3 4])
; {false (1 3), true (2 4)}
; frequencies: counts occurrences
(frequencies [:a :b :a :c :a])
; {:a 3, :b 1, :c 1}
; distinct: removes duplicates
(distinct [1 1 2 3 3]) ; (1 2 3)sort sorts and sort-by sorts by a function or key. group-by groups elements into a map, frequencies counts occurrences and distinct removes duplicates.
Competition
Atoms
; Atomic and synchronous mutable state: (def counter (atom 0)) ; Read (deref or @): @counter ; 0 (deref counter) ; 0 ; Update with swap!: (swap! counter inc) ; 1 (swap! counter + 10) ; 11 ; Reset (replaces): (reset! counter 0)
An atom holds mutable state in an atomic and synchronous way. It is read with @ or deref; updated with swap! (applies a function) or reset! (replaces).
alter and commute
(def account (ref 100)) ; alter: applies a function (order matters) (dosync (alter account - 30)) ; commute: for commutative operations ; (faster, no guaranteed order) (dosync (commute account + 50)) ; ref-set: replaces the value (dosync (ref-set account 0))
Inside dosync, alter applies a function keeping the order, while commute is optimized for commutative operations (like +). ref-set replaces the value.
swap! and validation
; swap! applies a function atomically: (def balance (atom 100)) (swap! balance - 30) ; 70 ; With extra arguments: (swap! balance + 50) ; 120 ; Validator (rejects invalid values): (def age (atom 0 :validator pos?)) (swap! age inc) ; 1 (ok) (reset! age -5) ; ERROR (validator fails)
swap! applies a function to the value of the atom atomically, retrying on conflict. A :validator rejects updates that return invalid values.
Agents
; Asynchronous queued update: (def logger (agent [])) ; send enqueues an action: (send logger conj "Event 1") (send logger conj "Event 2") ; Read (may be stale): @logger ; Wait for completion: (await logger) ; send-off for blocking actions (IO)
An agent manages state updated asynchronously. send enqueues actions processed one at a time; await waits for completion. Use send-off for IO operations.
Watchers
; add-watch observes changes:
(def counter (atom 0))
(add-watch counter :log
(fn [key ref old new]
(println old "->" new)))
(swap! counter inc) ; prints: 0 -> 1
(swap! counter inc) ; prints: 1 -> 2
; Remove:
(remove-watch counter :log)A watcher is a function called whenever an atom (or ref/agent) changes. It receives the key, the reference, the old value and the new one. It is removed with remove-watch.
Futures
; future: asynchronous computation (def result (future (Thread/sleep 2000) (* 42 42))) ; Do something else meanwhile... ; @ blocks until the value is ready: @result ; 1764 ; deref with timeout: (deref result 5000 :timeout) (realized? result) ; true
A future runs a computation on another thread and returns immediately. @ (deref) blocks until the value is ready. deref with a timeout returns a value if it expires.
Refs (STM)
; Refs: coordinated transactional state (def account-a (ref 1000)) (def account-b (ref 0)) ; Transaction with dosync: (dosync (alter account-a - 100) (alter account-b + 100)) @account-a ; 900 @account-b ; 100
refs allow updating several values in a coordinated way via Software Transactional Memory. Inside dosync, the changes are atomic — either all happen or none do.
Promises
; promise: value delivered once (def p (promise)) ; In one thread, deliver: (deliver p "value") ; In another, wait: @p ; "value" ; realized? tests if it has been delivered: (realized? p) ; true ; Useful for synchronizing threads
A promise is a one-way channel: a value is delivered once with deliver and whoever does deref waits until it exists. It is used to synchronize threads.
Advanced
Macros
; Macro: code that generates code
(defmacro unless [cond & body]
`(if (not ~cond)
(do ~@body)))
(unless false
(println "Runs!"))
; See the expansion:
(macroexpand '(unless true (println "x")))A macro transforms code at compile time, allowing you to create new syntactic forms. macroexpand shows the generated code, useful for debugging macros.
Multimethods
; defmulti: dispatch by function
(defmulti area :type)
(defmethod area :circle [{:keys [radius]}]
(* Math/PI radius radius))
(defmethod area :rectangle [{:keys [w h]}]
(* w h))
(area {:type :circle :radius 2}) ; 12.56
(area {:type :rectangle :w 3 :h 4}) ; 12A multimethod does dynamic dispatch based on the result of a function. defmulti defines the dispatch function and each defmethod handles a specific value.
Syntax quote
; ` = syntax quote (with namespace) ; ~ = unquote (evaluates expression) ; ~@ = unquote-splicing (inserts seq) `(if (not ~cond) (do ~@body)) ; Example: (let [x 5] `(+ ~x 1)) ; (clojure.core/+ 5 1) ; ~@ splices a list: (let [args '(1 2 3)] `(+ ~@args)) ; (+ 1 2 3)
In syntax quote, ` creates a code template, ~ (unquote) inserts the value of an expression and ~@ (unquote-splicing) splices a sequence of arguments.
reify
; reify: creates an anonymous object with protocols
(def descriptor
(reify Describable
(describe [this] "anonymous object")))
(describe descriptor) ; "anonymous object"
; Implement Java interfaces:
(reify Comparable
(compareTo [this other] 0))reify creates an anonymous object that implements protocols or Java interfaces, without defining a named type. It is useful for quick, one-off implementations.
Protocols
; Define a protocol (interface): (defprotocol Describable (describe [this])) ; Implement for types: (extend-protocol Describable String (describe [s] (str "String: " s)) Long (describe [n] (str "Number: " n))) (describe "hello") ; "String: hello" (describe 42) ; "Number: 42"
A protocol defines a set of functions (like an interface). extend-protocol implements it for existing types, allowing efficient type-based polymorphism.
Vars and dynamic binding
; ^:dynamic allows temporary re-binding: (def ^:dynamic *printer* :screen) (defn log [msg] (println *printer* msg)) ; binding changes only inside the block: (binding [*printer* :file] (log "saved")) ; :file saved (log "normal") ; :screen normal
A var marked ^:dynamic can be temporarily re-bound with binding, visible only inside the block. By convention, the name is surrounded by asterisks.
defrecord
; Record: named data type
(defrecord Person [name age])
; Create:
(def p (->Person "Anna" 30))
(map->Person {:name "Ray" :age 25})
; Access (like a map):
(:name p) ; "Anna"
; Record + protocol:
(defrecord Animal [name]
Describable
(describe [a] (str "Animal: " (:name a))))A defrecord creates a data type with named fields, accessible like a map. The ->Name and map->Name constructors are generated automatically, and it can implement protocols.
Exceptions
; try/catch/finally:
(try
(/ 1 0)
(catch ArithmeticException e
(println "Error:" (.getMessage e)))
(finally
(println "always runs")))
; Throw an exception:
(throw (ex-info "invalid" {:code 400}))
; try+ (slingshot) handles mapstry wraps code that may fail, catch handles the exception by type and finally always runs. throw raises exceptions; ex-info creates exceptions with data.
IO and Java Interop
Calling Java methods
; .method calls an instance method: (.toUpperCase "hello") ; "HELLO" (.length "world") ; 5 (.replace "a-b" "-" "+") ; "a+b" ; Static class/method: (Math/abs -42) ; 42 (Math/sqrt 16) ; 4.0 (System/currentTimeMillis) ; Static field: Math/PI ; 3.14159...
Java interop uses .method for instance methods and Class/method for static ones. Static fields are accessed with Class/FIELD, like Math/PI.
Writing files (writer)
(require '[clojure.java.io :the io])
; Write with a writer:
(with-open [w (io/writer "output.txt")]
(.write w "first line\n")
(.write w "second line\n"))
; Append:
(with-open [w (io/writer "log.txt"
:append true)]
(.write w "new entry\n"))io/writer creates a writer for controlled writing. with-open closes it automatically at the end. The :append true option appends to the existing file.
Creating objects and import
; A trailing dot creates an instance: (java.util.Date.) (java.util.ArrayList.) ; With arguments: (java.util.ArrayList. 10) ; Import for short names: (import '[java.time LocalDate]) (LocalDate/now) ; doto: configures an object with methods (doto (java.util.HashMap.) (.put "a" 1) (.put "b" 2))
The trailing dot (Class.) creates an instance. import lets you use short names. The doto macro calls several methods on the same object, useful for configuring mutable Java objects.
Standard input and output
; Print:
(println "Hello" "World") ; with newline
(print "no newline")
(prn {:a 1}) ; readable format (edn)
; Read from stdin:
(def line (read-line))
(println "You said:" line)
; Formatted printf:
(printf "Name: %s, Age: %d%n" "Anna" 30)println prints with a line break and print without. prn prints in edn format (readable by Clojure). read-line reads a line from stdin.
slurp and spit
; slurp: reads a whole file into a string (def content (slurp "data.txt")) ; spit: writes a string to a file (spit "output.txt" "Hello World") ; Append: (spit "log.txt" "line\n" :append true) ; Also works with URLs: ; (slurp "https://example.com")
slurp reads the entire content of a file into a String and spit writes a String to a file. The :append true option appends instead of overwriting.
Namespaces and require
; Declare namespace:
(ns my-app.core
(:require [clojure.string :the str]
[clojure.set :the set]
[my-app.utils :refer [help]]))
; Use:
(str/upper-case "hello") ; with alias
(set/union #{1} #{2})
(help) ; direct refer
; In the REPL:
(require '[clojure.string :the str])ns declares the namespace and its dependencies. :require loads namespaces, :the defines an alias and :refer imports specific functions for direct use.
Reading files (reader)
(require '[clojure.java.io :the io])
; Read line by line:
(with-open [r (io/reader "data.txt")]
(doseq [line (line-seq r)]
(println line)))
; Read all the a seq of lines:
(with-open [r (io/reader "data.txt")]
(doall (line-seq r)))The clojure.java.io namespace gives access to readers and writers. with-open ensures the resource is closed; line-seq returns a lazy sequence of lines.
Tools
Clojure CLI and deps.edn
# Main commands:
clojure -M -m my-app.core ; run
clojure -A:dev ; with alias
clojure -T:build uber ; build
# deps.edn defines dependencies:
{:deps
{org.clojure/clojure
{:mvn/version "1.11.1"}
ring/ring-core
{:mvn/version "1.10.0"}}}The Clojure CLI uses the deps.edn file to declare dependencies and aliases. The clojure -M command runs the program; aliases enable extra configurations.
Tests
(ns my-app.core-test
(:require [clojure.test :refer
[deftest is testing]]
[my-app.core :refer [add]]))
(deftest test-add
(testing "basic addition"
(is (= 5 (add 2 3)))
(is (= 0 (add -1 1)))))
; Run: clojure -M:test (or lein test)Tests use clojure.test: deftest defines a test, is checks an assertion and testing groups with a description. They run with lein test or via the CLI.
Leiningen
# Create a project: lein new app my-project # Main commands: lein run ; run lein repl ; REPL lein test ; tests lein uberjar ; executable JAR # project.clj defines dependencies: ; :dependencies ; [[org.clojure/clojure "1.11.1"]]
Leiningen is a classic build tool. lein new creates projects, lein run runs and lein uberjar generates a standalone JAR. Dependencies go in project.clj.
Best practices
; Prefer immutable data and pure functions:
(defn total [items]
(reduce + (map :price items)))
; Use keywords the access functions:
(map :name people)
; Threading for readable pipelines:
(->> data
(filter :active)
(map :value)
(reduce +))
; Predicate names end in ?:
(defn valid? [x] (pos? x))Favor immutable data, pure functions and keywords the accessors. Use threading macros for readable pipelines and end predicate names in ? and mutating functions in !.
REPL
# Start: clojure ; or: lein repl ; Useful REPL commands: (doc map) ; documentation (source map) ; source code (find-doc "lazy"); search docs (apropos "map") ; symbols with "map" ; Reload namespace: (require 'my-app.core :reload)
The REPL is Clojure's interactive environment. Functions like doc, source, find-doc and apropos help explore the standard library in real time.
Project structure
my-project/
deps.edn ; dependencies
src/
my_app/
core.clj ; ns my-app.core
utils.clj ; ns my-app.utils
test/
my_app/
core_test.clj
resources/ ; static files
; _ in the file = - in the namespace:
; my_app/core.clj -> my-app.coreA project organizes code into src/ (with namespaces) and test/. The file path defines the namespace: underscores (_) in the file become hyphens (-) in the name.