views:

213

answers:

4

Embarrassingly enough, I'm having some trouble designing this macro correctly.

This is the macro as I have it written:

(defmacro construct-vertices
  [xs ys]
  (cons 'draw-line-strip
        (map #(list vertex %1 %2) xs ys)))

It needs to take in two collections or seqs, xs and ys, and I need it to give me…

(draw-line-strip (vertex 0 1) (vertex 1 1) 
                 (vertex 3 3) (vertex 5 6) 
                 (vertex 7 8))

…for xs = [0 1 3 5 7] and ys = [1 1 3 6 8].

This works just fine if I give my macro plain 'n' simple vectors (e.g. [1 2 3 4] and [2 3 4 5]) but doesn't work if I give it a lazy-seq/anything that needs to be evaluated like (take 16 (iterate #(+ 0.1 %1) 0)) and (take 16 (cycle [0 -0.1 0 0.1])))).

I realize that this is because these are passed to the macro unevaluated, and so I get, for example, (vertex take take) as my first result (I do believe). Unfortunately, everything I've tried to first evaluate these and then carry out my macro-rewriting has failed/looked terribly hacky.

I'm sure I'm missing some sort of basic syntax-quote/unquote pattern here–I'd love some help/pointers!

Thanks so much.

EDIT I should mention, draw-line-strip is a macro, and vertex creates an OpenGL vertex; they are both part of the Penumbra Clojure+OpenGL library.

EDIT 2 This is for a custom graphing tool I need, and the primary motivation for creating it was to be faster than JFreeCharts and company.

EDIT 3 I suppose I should note that I do have a macro version working, it's just horrid and hacky as I mentioned above. It uses eval, as demonstrated below, but like this:

(defmacro construct-vertices
  [xs ys]
  (cons 'draw-line-strip
        (map #(list vertex %1 %2) (eval xs) (eval ys))))

Unfortunately, I get…

error: java.lang.ClassFormatError: Invalid this class index 3171 in constant pool in class file tl/core$draw_l$fn__9357 (core.clj:14)

…when using this with a few thousand-item long list(s). This is because I'm writing far too much into the pre-compiled code, and the classfile can't handle (I suppose) that much data/code. It looks like I need to, somehow, obtain a function version of draw-line-strip, as has been suggested.

I'm still open, however, to a more elegant, less hackish, macro solution to this problem. If one exists!

+2  A: 

Why not something like this, using function instead of macro:

(defn construct-vertices [xs ys]
  (apply draw-line-strip (map #(list vertex %1 %2) xs ys)))

That should call draw-line-strip with required args. This example is not the best fit for macros, which shouldn't be used where functions can do.

Note: I didn't try it since I don't have slime set up on this box.

EDIT: Looking again, I don't know if you want to evaluate vertex before calling draw-line-strip. In that case function will look like:

(defn construct-vertices [xs ys]
  (apply draw-line-strip (map #(vertex %1 %2) xs ys)))
Dev er dev
I'll comment here, too: `draw-line-strip` is a macro, therefore `apply` doesn't work with it. Otherwise I'd be happily using it!
Isaac Hodes
+2  A: 

If you really need draw-line-strip to be a macro and you want a fully general method of doing what the question text describes and you don't care too much about a bit of a performance hit, you could use eval:

(defn construct-vertices [xs ys]
  (eval `(draw-line-strip ~@(map #(list 'vertex %1 %2) xs ys))))
                                      ; ^- not sure what vertex is
                                      ;    and thus whether you need this quote

Note that this is terrible style unless it is really necessary.

Michał Marczyk
Note that the above is a function, not a macro, and thus has the runtime values of `xs` and `ys` available. These are then used to construct a `draw-line-strip` form which is macroexpanded and compiled at runtime. There is of course a certain performance cost to this, but it really is the only way of doing what the question asks for except for rewriting `draw-line-strip` not to be a macro (which, if possible, would likely be the cleanest solution).
Michał Marczyk
Doesn't vertex need to be syntax-quoted? Edit: ah, no since it's getting eval's in the same ns.
Alex Taggart
+1  A: 

This looks like a typical problem with some of the macro systems in Lisp. The usual Lisp literature applies. For example On Lisp by Paul Graham (uses Common Lisp).

Usually a macro uses the source it encloses and generates new source. If a macro call is (foo bar), and the macro should generate something different based on the value of bar, then this is generally not possible, since the value of bar is generally not available for the compiler. BAR really has only a value at runtime, not when the compiler expands the macros. So one would need to generate the right code at runtime - which might be possible, but which is usually seen as bad style.

In these macro systems macros can't be applied. A typical solution looks like this (Common Lisp):

(apply (lambda (a b c)
          (a-macro-with-three-args a b c))
       list-of-three-elements)

It is not always possible to use above solution, though. For example when the number of arguments varies.

It's also not a good idea that DRAW-LINE-STRIP is a macro. It should better be written as a function.

Rainer Joswig
+2  A: 

I looked at the macro expansion for draw-line-strip and noticed that it just wraps the body in a binding, gl-begin, and gl-end. So you can put whatever code inside it you want.

So

(defn construct-vertices [xs ys]
  (draw-line-strip
    (dorun (map #(vertex %1 %2) xs ys))))

should work.

dreish
That's something I didn't try…looking at the expansion. I didn't even think of that! Thank you very much.
Isaac Hodes
You're very welcome. I just noticed the unnecessary use of #(... %1 %2), too, so better yet: (defn construct-vertices [xs ys] (draw-line-strip (dorun (map vertex xs ys))))
dreish