2
votes

I'm having trouble getting a clojure defmacro to do what I want. I've reduced my actual code down to the following snippet.

This creates something close to what I want. I am trying to conditionally insert either (first p#) or (second p#) depending on a parameter passed into the macro.

(defmacro mmz1 [t]
  `(map (fn [p#] (let [t1# (first p#)
                      t2# ~(if t `(first p#) `(second p#))]
                  (* t1# t2#)))
        [ [1 2] [3 4] ]))

(macroexpand-1 '(mmz1 false))

shows

(map
  (fn [p__18341__auto__]
    (let [t1__18342__auto__ (first p__18341__auto__)
          t2__18343__auto__ (second p__18340__auto__)]
      (* t1__18342__auto__ t2__18343__auto__)))
  [[1 2] [3 4]])

But, note that the variable in this form (second p_18340_auto_) does not match the anonymous function argument p_18341_auto_. So, executing the code results in an error since that second var is not defined. How can I get these vars to match? This is what I would like to accomplish.

For testing purposes, this code accomplishes what I want, but I don't want the (if) form in the resulting macro code that sets t2#. A macro should allow me to do this--shouldn't it?

(defmacro mmz0 [t]
  `(map (fn [p#] (let [t1# (first p#)
                      t2# (if ~t (first p#) (second p#))]
                  (* t1# t2#)))
        [ [1 2] [3 4] ]))

(macroexpand-1 '(mmz0 false))

shows

(map
  (fn [p__18387__auto__]
    (let [t1__18388__auto__ (first p__18387__auto__)
          t2__18389__auto__ (if false
                              (first p__18387__auto__)
                              (second p__18387__auto__))]
      (* t1__18388__auto__ t2__18389__auto__)))
  [[1 2] [3 4]])

and the output of the code is the expected:

(mmz0 false) -> (2 12)
2
If you want the background behind this issue, I explained it (somewhat) in this query on the Overtone forums. groups.google.com/forum/?fromgroups=#!topic/overtone/… - Roger Allen

2 Answers

3
votes

One of the solutions

(defmacro mmz1 [t]
  `(map (fn [p#] (let [t1# (first p#)
                       t2# (~(if t 'first 'second) p#)]
                   (* t1# t2#)))
        [ [1 2] [3 4] ]))

Update. More general solution

(defmacro mmz1 [t]
  (let [trg-fn (if t 
                 `(fn [p#] (first p#)) 
                 `(fn [p#] (second p#)))]
    `(map (fn [p#] (let [t1# (first p#)
                         t2# (~trg-fn p#)]
                   (* t1# t2#)))
        [ [1 2] [3 4] ])))

You can replace (fn [p#]...) in if branches with more complex functions.

Update2. Simpler solution that uses predefined function formal parameter p

(defmacro mmz1 [t]
  (let [p `p#]
   `(map (fn [~p] (let [t1# (first ~p)
                        t2# ~(if t `(first ~p) `(second ~p))]
                    (* t1# t2#)))
         [ [1 2] [3 4] ])))
0
votes

It has to be stated that this has no need to be a macro at all.

The function equivalent is more readable and more composable, so I recommend you use that.