Macros in Clojure
Macros are one of the most distinctive and powerful features of Clojure. They allow developers to extend the language by writing code that generates code. Unlike functions, macros operate on unevaluated forms, giving you control over how expressions are expanded and executed.
🌍 What Are Macros?
- Code as Data: Clojure is homoiconic, meaning its code is represented as data structures (lists, vectors, maps). Macros exploit this property to manipulate code directly.
- Difference from Functions: Functions evaluate their arguments before execution, while macros can decide whether or not to evaluate arguments.
- Purpose: Macros are used to create new syntactic constructs, embed domain-specific languages, or simplify repetitive patterns.
🛠️ Defining a Macro
Macros are defined using defmacro. The body of a macro should return a Clojure form that can be evaluated as code.
Example 1: A Simple Conditional Macro
(defmacro when-positive [x & body]
`(if (pos? ~x)
(do ~@body)))
;; Usage
(when-positive 5
(println "The number is positive!")
(println "This block only runs if x > 0"))
Explanation:
~unquotes a value into the returned form.~@splices a sequence of forms into the returned code.- The macro expands into an
ifstatement with adoblock.
Example 2: Threading Macro (->)
Clojure’s built-in -> macro rewrites nested function calls into a linear flow.
(-> {}
(assoc :a 1)
(assoc :b 2))
;; Expands to:
(assoc (assoc {} :a 1) :b 2)
This makes code more readable by avoiding deeply nested parentheses. Clojure
Example 3: Custom Logging Macro
(defmacro log-expr [expr]
`(let [result# ~expr]
(println "Evaluating:" '~expr "=>" result#)
result#))
;; Usage
(log-expr (+ 2 3))
;; Output: Evaluating: (+ 2 3) => 5
Here, result# is a gensym (unique symbol) automatically generated to avoid naming conflicts.
🔎 Inspecting Macros
You can use macroexpand or macroexpand-1 to see how a macro transforms code:
(macroexpand '(when-positive 5 (println "Positive!")))
;; => (if (pos? 5) (do (println "Positive!")))
⚡ Best Practices
- Use macros sparingly: Prefer functions unless you need control over evaluation.
- Test with macroexpand: Always inspect macro expansions to ensure correctness.
- Keep macros simple: Complex macros can make code harder to read and debug.
📖 Conclusion
Macros in Clojure empower developers to extend the language itself, enabling expressive and concise code. By leveraging homoiconicity, macros let you manipulate code as data, opening doors to new syntactic constructs and domain-specific abstractions.
No comments:
Post a Comment