Skip to content

Commit fa39369

Browse files
committed
Add examples and rationale to the Macros section
All five macro rules were bare statements with no code examples or explanations. Add idiomatic good/bad examples and brief rationale to each: dont-write-macro-if-fn-will-do, write-macro-usage-before- writing-the-macro, break-complicated-macros, macros-as-syntactic- sugar, and syntax-quoted-forms.
1 parent 0a72ead commit fa39369

File tree

1 file changed

+108
-1
lines changed

1 file changed

+108
-1
lines changed

README.adoc

Lines changed: 108 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2267,25 +2267,132 @@ catch specific Errors.
22672267

22682268
=== Don't Write a Macro If a Function Will Do [[dont-write-macro-if-fn-will-do]]
22692269

2270-
Don't write a macro if a function will do.
2270+
Don't write a macro if a function will do. Macros cannot be passed to
2271+
higher-order functions like `map`, `filter`, or `comp`, and they make
2272+
code harder to reason about. Reserve macros for when you truly need
2273+
control over evaluation.
2274+
2275+
[source,clojure]
2276+
----
2277+
;; good - a plain function works fine here
2278+
(defn square [x]
2279+
(* x x))
2280+
2281+
;; bad - a macro that does nothing a function can't do
2282+
(defmacro square [x]
2283+
`(* ~x ~x))
2284+
----
22712285

22722286
=== Write Macro Usage before Writing the Macro [[write-macro-usage-before-writing-the-macro]]
22732287

22742288
Create an example of a macro usage first and the macro afterwards.
2289+
Writing the desired call site first helps you design a clean API and
2290+
avoids over-engineering the macro.
2291+
2292+
[source,clojure]
2293+
----
2294+
;; Step 1: Write down how you want the macro to be used
2295+
(with-retry {:retries 3 :delay 1000}
2296+
(fetch-data url))
2297+
2298+
;; Step 2: Then implement the macro to support that usage
2299+
(defmacro with-retry [{:keys [retries delay]} & body]
2300+
...)
2301+
----
22752302

22762303
=== Break Complicated Macros [[break-complicated-macros]]
22772304

22782305
Break complicated macros into smaller functions whenever possible.
2306+
Helper functions can be tested independently and called from other
2307+
code, while macro internals cannot.
2308+
2309+
[source,clojure]
2310+
----
2311+
;; good - helper functions handle parts of the code generation
2312+
(defn- emit-constructor [name fields]
2313+
`(defn ~(symbol (str "make-" name)) [~@fields]
2314+
(new ~name ~@fields)))
2315+
2316+
(defn- emit-predicate [name]
2317+
`(defn ~(symbol (str name "?")) [x#]
2318+
(instance? ~name x#)))
2319+
2320+
(defmacro defentity [name & fields]
2321+
`(do
2322+
(defrecord ~name [~@fields])
2323+
~(emit-constructor name fields)
2324+
~(emit-predicate name)))
2325+
2326+
;; bad - everything inlined in one large macro
2327+
(defmacro defentity [name & fields]
2328+
`(do
2329+
(defrecord ~name [~@fields])
2330+
(defn ~(symbol (str "make-" name)) [~@fields]
2331+
(new ~name ~@fields))
2332+
(defn ~(symbol (str name "?")) [x#]
2333+
(instance? ~name x#))))
2334+
----
22792335

22802336
=== Macros as Syntactic Sugar [[macros-as-syntactic-sugar]]
22812337

22822338
A macro should usually just provide syntactic sugar and the core of
22832339
the macro should be a plain function. Doing so will improve
22842340
composability.
22852341

2342+
[source,clojure]
2343+
----
2344+
;; good - the macro is thin sugar over a function
2345+
(defn perform-transaction [db-spec func]
2346+
(let [conn (get-connection db-spec)]
2347+
(try
2348+
(.setAutoCommit conn false)
2349+
(let [result (func conn)]
2350+
(.commit conn)
2351+
result)
2352+
(catch Exception e
2353+
(.rollback conn)
2354+
(throw e))
2355+
(finally
2356+
(.close conn)))))
2357+
2358+
(defmacro with-transaction [binding & body]
2359+
`(perform-transaction ~(second binding)
2360+
(fn [~(first binding)] ~@body)))
2361+
2362+
;; bad - all logic lives in the macro
2363+
(defmacro with-transaction [binding & body]
2364+
`(let [conn# (get-connection ~(second binding))]
2365+
(try
2366+
(.setAutoCommit conn# false)
2367+
(let [~(first binding) conn#
2368+
result# (do ~@body)]
2369+
(.commit conn#)
2370+
result#)
2371+
(catch Exception e#
2372+
(.rollback conn#)
2373+
(throw e#))
2374+
(finally
2375+
(.close conn#)))))
2376+
----
2377+
22862378
=== Syntax Quoted Forms [[syntax-quoted-forms]]
22872379

22882380
Prefer syntax-quoted forms over building lists manually.
2381+
Syntax-quote (backtick) auto-qualifies symbols, preventing accidental
2382+
capture, and is far easier to read than nested `list`/`cons` calls.
2383+
2384+
[source,clojure]
2385+
----
2386+
;; good - syntax-quote makes the output structure obvious
2387+
(defmacro when-not [test & body]
2388+
`(when (not ~test)
2389+
~@body))
2390+
2391+
;; bad - manual list construction is hard to follow
2392+
(defmacro when-not [test & body]
2393+
(list 'when (list 'not test)
2394+
(cons 'do body)))
2395+
----
22892396

22902397
== Common Metadata
22912398

0 commit comments

Comments
 (0)