Last active
November 11, 2021 09:48
-
-
Save peterwang/9e0d73ec3054052217e5280349471dfe to your computer and use it in GitHub Desktop.
Clojure Macro-writing Macros step by step.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;;; 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