简体   繁体   中英

Calling a macro on a macro

I'm trying to write a Clojure macro that creates a prefix notation list for evaluation from a simple infix notation list, say (2 * 3 + 4 * 2) to an evaluated (+ (* 2 3) (*4 2)) (resulting in 14 being returned).

I have written the following code:

(defmacro infix [op inlist]
  (let [[i1 i2 & i3] inlist
        last-group? (nil? (second i3))]  

    (if last-group?
      `(if (= ~op ~i2)
        (~i2 ~i1 ~(first i3)) ; return unevaluated prefix list
        (~i1 ~i2 ~(first i3))) ; return unevaluated infix list
      `(if (= ~op ~i2)
         ; recur with prefix list in i1 position
         (infix ~op ~(conj (rest i3) (list i2 i1 (first i3)) ))
         ; return as list: i1 and i2, recur i3 (probably wrong)
         (~i1 ~i2 (infix ~op ~i3))
         ))))

With the intention of enforcing operator precedence by calling the macro recursively with different op (operator function) parameters:

(infix + (infix * (2 * 3 + 4 * 2)))

Above, I'm just using it with two * and + , but ultimately I'd want to call the macro for all (or at least for the sake of this exercise , / * + -) operators.

When I execute the above nested macro call, I get the following error:

CompilerException java.lang.RuntimeException: Can't take value of a macro: #'cbat.ch7.ex2/infix, compiling:(/tmp/form-init4661580047453041691.clj:1:1)

Calling the macro for a single operator and a list of the same operator (ie (infix * (2 * 3 * 4)) ) works as expected. If I call the macro with a single (i1 i2 i3) list, if op differs from i2 , it tries to (understandably) return the unevaluated infix list with the error:

ClassCastException java.lang.Long cannot be cast to clojure.lang.IFn  cbat.ch7.ex2/eval3003 (form-init4661580047453041691.clj:1)

I was hoping calling the macro recursively would mean that I could process the unevaluated infix list before the entire line was evaluated, but this doesn't seem to work.


I'm pretty sure the else branch of the latter, inner if (ie (~i1 ~i2 (infix ~op ~i3)) ) is incorrect and I may just need the inner infix call, but I'm more concerned with getting the nested macro calls for the different operators working prior to evaluation.

I know that this isn't the usual way of converting infix to prefix notation, and have since found out about Dijkstra's shunting-yard algorithm , but please could someone kindly enlighten me as to:

  1. whether such nested macro calls are possible?
  2. whether my logic is reasonable, and not too far from a solution? If so...
  3. ... what changes I need to make to get things running?

I'm really focused on learning Clojure, so any thorough explanation (where possible) will be most welcome.

You can nest macro calls as this code demonstrates:

(defmacro mac [tag & forms]
  `(do
     (println "mac - enter" ~tag)
     ~@forms
     (println "mac - exit " ~tag)))

(mac :a
  (doseq [i (range 3)]
    (mac :b (println i))))

mac - enter :a
mac - enter :b
0
mac - exit  :b
mac - enter :b
1
mac - exit  :b
mac - enter :b
2
mac - exit  :b
mac - exit  :a

You can also make recursive macro calls as this shows:

(defmacro macr [n]
  (if (zero? n)
    1
    `(* ~n (macr ~(dec n)))))

(macr 5)   => 120

Without delving too deep into your particular implementation, I would suggest 2 points:

  1. At least to start, keep your forms as simple as possible. This means only forms like (2 + 3) . And especially don't force the macro to figure out operator precedence in the early versions (or ever!).

  2. Macros are almost never necessary, and it is unfortunate IMHO that they are somewhat "over-hyped" when learning Clojure & other lisps. I would suggest you don't even think about them for the first year or two, as they are more brittle than functions and less powerful in important ways (you can't pass a macro into a function, for example).

Update

Whenever you want to write something complicated (a macro definitely qualifies!), start small and build it up one step at a time. Using the lein-test-refresh plugin and the Tupelo library definitely help here.

First, make the simplest possible macro and observe its behavior:

(ns tst.clj.core
  (:use clj.core clojure.test tupelo.test)
  (:require [tupelo.core :as t] ))
(t/refer-tupelo)

(defn infix-fn [[a op b]]
  (spyx a)
  (spyx op)
  (spyx b)
  )

(defmacro infix [form]
  (infix-fn form))

(infix (2 + 3))

a => 2
op => +
b => 3

For many macros, it is helpfully to send the marcro args to a helper function like infix-fn . The spyx helps us by printing the symbol and its value. At this point, we can simply re-order the args into prefix notation and away we go:

(defn infix-fn [[a op b]] (list op a b))

(defmacro infix [form] (infix-fn form))

(deftest master
  (is= 5 (infix (2 + 3)))
  (is= 6 (infix (2 * 3))))

What if we have a recursive tree structure? Check if we need to recurse in infix-fn :

(declare infix-fn)

(defn simplify [arg]
  (if (list? arg)
    (infix-fn arg)
    arg))

(defn infix-fn [[a op b]]
  (list op (simplify a) (simplify b)))

(is= 7 (infix ((2 * 2) + 3)))
(is= 9 (infix ((1 + 2) * 3)))


(is= 35 (infix ((2 + 3) * (3 + 4))))
(is= 26 (infix ((2 * 3) + (4 * 5))))

I would not want to add in the complication of operator precedence. If absolutely necessary, I would not code it up myself but would use the excellent Instaparse library for that purpose.

expansion of your call would give you a clue:

(if (= + *) 
  (* infix (2 * 3 + 4 * 2)) 
  (infix * (2 * 3 + 4 * 2)))

You've got the wrong presumption that the argument of macro would be expanded before the macro itself, i guess. But in fact in this one: (~i2 ~i1 ~(first i3)) i1 is still infix symbol. As far as i can see, the solution is to add some new condition branch, treating infix form some special way.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM