Skip to content

Instantly share code, notes, and snippets.

@tomfaulhaber
Created March 8, 2012 05:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tomfaulhaber/1998992 to your computer and use it in GitHub Desktop.
Save tomfaulhaber/1998992 to your computer and use it in GitHub Desktop.
(ns example.macros)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; This is some example code to give you an understanding of how
;; macros work. As a disclaimer, it is not the best way to implement
;; this and it does not cover all the cases. Instead, it is designed
;; to show how "code is data" makes macros possible.
;; If you'd like to build similar code, I recommend you investigate
;; clojure.walk which abstracts away some of this structure traversal
;; and lets you focus on the actual transformations that you wish to
;; perform.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Code is just data. For example, we can set a var to be equal to
;; a block of code as we do here. Note that the code is a list (with lists
;; and a vector inside) and not a string.
(def some-code '(defn safe-div [numerator divisor]
(unless (zero? divisor) (/ numerator divisor))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Like any other data, we can create a function to trasform the
;; code. Here we have a function that takes a code structure and
;; changes (unless c statements) to (if (not c) statements)
(defn rewrite-unless [form]
(if (and (seq? form) (seq form))
(let [[fun & body] form
expanded-body (map rewrite-unless body)]
(if (= fun 'unless)
(let [[test & unless-body] expanded-body]
(concat
(list 'if (list 'not test))
unless-body))
(conj expanded-body fun)))
form))
;; Now if we run "(rewrite-unless some-code)" we get:
;; (defn safe-div [numerator divisor]
;; (if (not (zero? divisor)) (/ numerator divisor)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; In rewrite-unless, some of the code was general data structure
;; manipulation and some of the code described how to turn unless
;; into a combination of if and not. Let's refactor this so that we
;; have a function that can rewrite data structure according to a
;; variety of different rules.
;; rewrite takes a map in which the key is a symbol and the value is a function.
;; It walks the form and, when the first element of a sub-form is a key
;; in the map, run the value function on the remaining elements of the
;; sub-form, replacing the sub-form with the result of the function.
(defn rewrite [macro-map form]
(if (and (seq? form) (seq form))
(let [[fun & body] form
expanded-body (map (partial rewrite macro-map) body)
macro-fn (get macro-map fun)]
(if macro-fn
(apply macro-fn expanded-body)
(conj expanded-body fun)))
form))
;; Now we can construct a map that has our unless macro. We could add all
;; sorts of different "macros" here.
(def macro-map {'unless (fn [test & body]
(concat
(list 'if (list 'not test))
body))})
;; If we run "(rewrite macro-map some-code)" we get the same as before:
;; (defn safe-div [numerator divisor]
;; (if (not (zero? divisor)) (/ numerator divisor)))
;;; Now use systax quote to simplify
;;; Original version:
(def macro-map {'unless (fn [test & body]
(concat
(list 'if (list 'not test))
body))})
;;; New version, with syntax quote
(def macro-map-1 {'unless (fn [test & body]
`(if (not ~test) ~@body))})
;;; Creating unique variables
(defmacro my-if-let [[var-name test & more-bindings] true-side & false-side]
`(let [test-result# ~test]
(if test-result#
(let [~var-name test-result#
~@more-bindings]
~true-side)
~@false-side)))
(my-if-let [more (next '(a b c))]
(first more)
27)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment