Created
March 8, 2012 05:50
-
-
Save tomfaulhaber/1998992 to your computer and use it in GitHub Desktop.
This file contains 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
(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