Skip to content

Instantly share code, notes, and snippets.

@peterwang
Last active November 11, 2021 09:48
Show Gist options
  • Save peterwang/9e0d73ec3054052217e5280349471dfe to your computer and use it in GitHub Desktop.
Save peterwang/9e0d73ec3054052217e5280349471dfe to your computer and use it in GitHub Desktop.
Clojure Macro-writing Macros step by step.
;;; Clojure Macro-writing Macros step by step.
(ns my.macros)
;; => nil
;;;;;;;; ns and vars
*ns*
;; => #namespace[my.macros]
(def x 3)
;; => #'my.macros/x
(def xs '(4 5))
;; => #'my.macros/xs
;;;;;;;; x
(-> (read-string "x"))
;; => x
(-> (read-string "x") str)
;; => "x"
(-> (read-string "x") eval)
;; => 3
x
;; => 3
;;;;;;;; 'x
(-> (read-string "'x"))
;; => 'x
(-> (read-string "'x") str)
;; => "(quote x)"
(-> (read-string "'x") eval)
;; => x
'x
;; => x
;;;;;;;; `x
(-> (read-string "`x"))
;; => 'my.macros/x
(-> (read-string "`x") str)
;; => "(quote my.macros/x)"
(-> (read-string "`x") eval)
;; => my.macros/x
`x
;; => my.macros/x
;;;;;;;; `(x)
(-> (read-string "`(x)"))
;; => (clojure.core/seq
;; (clojure.core/concat (clojure.core/list 'my.macros/x)))
(-> (read-string "`(x)") str)
;; => "(clojure.core/seq (clojure.core/concat (clojure.core/list (quote my.macros/x))))"
(-> (read-string "`(x)") eval)
;; => (my.macros/x)
`(x)
;; => (my.macros/x)
;;;;;;;; `~x
(-> (read-string "`~x"))
;; => x
(-> (read-string "`~x") str)
;; => "x"
(-> (read-string "`~x") eval)
;; => 3
`~x
;; => 3
;;;;;;;; `~@x
(-> (read-string "`~@x"))
(-> (read-string "`~@x") str)
(-> (read-string "`~@x") eval)
`~@xs
;; => #error {
;; :cause "splice not in list"
;; :via
;; ...
;; }
;;;;;;;; +
`+
;; => clojure.core/+
`(+)
;; => (clojure.core/+)
;;;;;;;; ~'+
`(~(quote +))
;; => (+)
`(~'+)
;; => (+)
;;;;;;;; `() as template for building expressions
`(+ x)
;; => (clojure.core/+ my.macros/x)
`(+ ~x)
;; => (clojure.core/+ 3)
`(+ ~xs)
;; => (clojure.core/+ (4 5))
`(+ ~x ~xs)
;; => (clojure.core/+ 3 (4 5))
`(+ ~x ~@xs)
;; => (clojure.core/+ 3 4 5)
(defmacro mac0 []
`(+ ~x ~@xs))
;; => #'my.macros/mac0
(macroexpand-1 '(mac0))
;; => (clojure.core/+ 3 4 5)
(mac0)
;; => 12
(defmacro mac1 [x & xs]
`(+ ~x ~@xs))
;; => #'my.macros/mac1
(macroexpand-1 '(mac1 3 4 5))
;; => (clojure.core/+ 3 4 5)
(mac1 3 4 5)
;; => 12
;;;;;;;; _evaluate once only_ pattern
(let [expr '(+ 1 2)]
`(let [val# ~expr]
(* val# val#)))
;; => (clojure.core/let
;; [val__22930__auto__ (+ 1 2)]
;; (clojure.core/* val__22930__auto__ val__22930__auto__))
(defmacro square [expr]
`(let [val# ~expr]
(* val# val#)))
;; => #'my.macros/square
(macroexpand-1 '(square (+ 1 2)))
;; => (clojure.core/let
;; [val__22937__auto__ (+ 1 2)]
;; (clojure.core/* val__22937__auto__ val__22937__auto__))
(square (+ 1 2))
;; => 9
;;;;;;;; TODO: bridge the gap
;;;;;;;; Macro-writing Macros
(defmacro once-only [names & body]
(let [gensyms (for [n names] (gensym (name n)))]
`(let [~@(interleave gensyms (repeat '(gensym "once")))] ; let form in the user-macro calling once-only
`(let [~~@(interleave gensyms names)] ; let form in the final expansion, generated by user-macro
~(let [~@(interleave names gensyms)] ; let form in the user-macro calling once-only
~@body)))))
;; => #'my.macros/once-only
;;;;;;;; Manually step, avoiding the nested (seq (concat (list ...))) madness
(let [x '(+ 1 2) y '(* 3 4)]
(once-only [x y] `(+ ~x ~y)))
;; => (clojure.core/let
;; [once22990 (+ 1 2) once22991 (* 3 4)]
;; (clojure.core/+ once22990 once22991))
(let [x '(+ 1 2) y '(* 3 4)]
(let [x22915 (gensym "once") y22916 (gensym "once")]
`(let [~@(list x22915 x y22916 y)] ; let form in the final expansion
~(let [x x22915 y y22916]
`(+ ~x ~y)))))
;; => (clojure.core/let
;; [once22998 (+ 1 2) once22999 (* 3 4)]
;; (clojure.core/+ once22998 once22999))
;;;;;;;; user-macro
(defmacro mac2 [x y]
(once-only [x y]
`(+ ~x ~y)))
;; => #'my.macros/mac2
(macroexpand-1 '(mac2 (+ 1 2) (* 3 4)))
;; => (clojure.core/let
;; [once23013 (+ 1 2) once23014 (* 3 4)]
;; (clojure.core/+ once23013 once23014))
(mac2 (+ 1 2) (* 3 4))
;; => 15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment