Search This Blog

Wednesday, February 17, 2010

Watching a macro as it expands

;; macros are code transformers
;; in a lisp, that means tree transformers
;; let's look at the -> macro, built into clojure.

(use 'clojure.contrib.repl-utils)
(source ->)

;; (defmacro ->
;;   "Threads the expr through the forms. Inserts x as the
;;   second item in the first form, making a list of it if it is not a
;;   list already. If there are more forms, inserts the first form as the
;;   second item in second form, etc."
;;   ([x] x)
;;   ([x form] (if (seq? form)
;;               (with-meta `(~(first form) ~x ~@(next form)) (meta form))
;;               (list form x)))
;;   ([x form & more] `(-> (-> ~x ~form) ~@more)))



;; What's 20C in Fahrenheit?
;; Multiply by nine, divide by 5, add 32
(-> 20 (* 9) (/ 5) (+ 32))

;; let's look at the tree for that
(use 'clojure.inspector)
(inspect-tree '(-> 20 (* 9) (/ 5) (+ 32)))

;; What does that look like after macroexpansion?
(use 'clojure.walk)
(macroexpand-all '(-> 20 (* 9) (/ 5) (+ 32)))
(inspect-tree (macroexpand-all '(-> 20 (* 9) (/ 5) (+ 32))))


;; How does the threading macro work?
(macroexpand-1 '(-> 20 (* 9) (/ 5) (+ 32)))

;; We can follow the recursion by using macroexpand-1 on each subexpression in turn.

;;slightly tidied up, stages are:

(macroexpand-1 '(-> 20 (* 9) (/ 5) (+ 32)))

(macroexpand-1
'(-> 
  (-> 20 (* 9))
  (/ 5) (+ 32)))

(macroexpand-1
 '(-> 
   (-> 
    (-> 20 (* 9)) 
    (/ 5)) 
   (+ 32)))

(macroexpand-1
 '(+ 
   (-> 
    (-> 20 (* 9)) 
    (/ 5))
   32))

`(+ 
  ~(macroexpand-1 '
    (-> 
     (-> 20 (* 9)) 
     (/ 5)))
  32)

`(+ 
  (/ 
   ~(macroexpand-1 '
     (-> 20 (* 9)))
   5)
  32)

(+ 
 (/ 
  (* 20 9) 
  5) 
 32)

;;This is a bit long-winded, how would we do this automatically?

(defn slow-tree-transformer [fn tree]
  (cond (not (seq? tree)) (fn tree)
        (empty? tree) '()
        :else (let [nxt (fn tree)]
                (if (not (= nxt tree)) nxt
                    (let [nxt (fn (first tree))]
                      (if (= nxt (first tree))
                        (let [nxt (slow-tree-transformer fn (first tree))]
                          (if (= nxt (first tree))
                            (cons (first tree) (slow-tree-transformer fn (rest tree)))
                            (cons nxt (rest tree))))
                        (cons nxt (rest tree))))))))


(def f (fn[x] (slow-tree-transformer macroexpand-1 x)))

(defn iterate-to-stable [fn start]
  (let [next (fn start)]
    (if (= next start) (list start)
        (cons start (iterate-to-stable fn next)))))

(use 'clojure.contrib.pprint)
(println "-----------------------------------")
(doall (map (fn [x] (pprint x) (println)) (iterate-to-stable f '(-> 20 (* 9) (/ 5) (+ 32)))))
(println "-----------------------------------")




No comments:

Post a Comment

Followers