tags:

views:

222

answers:

4

Suppose I've got a bunch of namespaces (apple, banana, orange). In these namespaces I use the eat macro, which calls (not "generates", calls) the peel function. The peel function is different for each fruit, but the macros are identical, and fairly big, so I'd like to create a fruit namespace that contains the eat macro. But when I call the eat macro from the apple namespace, the eat macro should call the apple/peel function.

To illustrate (but this doesn't work):

(ns fruit)
(defmacro eat [] (peel))

(ns apple)
(defn peel [] (prn "peeled apple"))
(fruit/eat)

(ns banana)
(defn peel [] (prn "peeled banana"))
(fruit/eat)

To emphasize, this means that the peel function should be called when, and only when, the macro is expanded, as in this example.

(ns apple)
(defn peel [] (prn "peeled apple"))
(defmacro eat [] (peel))
(macroexpand-1 '(eat))

So, any ideas on how to combine macros and polymorphism?

+1  A: 

EDIT: Sorry. I alread posted the following. But you're saying "calls, not generates" the peel function. So what I've written is probably not what you want although it seems it would get the intended result.

Simply quoting (peel) worked for me.

(ns fruit)
(defmacro eat [] '(peel))

(ns apple)
(defn peel [] (prn "peeled apple"))
(fruit/eat)

(ns banana)
(defn peel [] (prn "peeled banana"))
(fruit/eat)
z5h
Thank you, but indeed not what I meant. It does get the desired result here, but not in my actual use case.
Michiel de Mare
+2  A: 
(defmacro eat [] ((var-get (resolve 'peel))))

Note that you are abusing namespaces, though.

Brian
+3  A: 

What you're describing is not polymorphism but what is called local capture. You want the eat macro to "capture" the local definition of peel.

This is considered bad style in most Lisps, especially Clojure, as it can lead to subtle and unpredictable bugs.

A better solution is to pass the correct peel to the eat macro when you call it:

(ns fruit)
(defmacro eat [peeler] `(~peeler))

(ns apple)
(defn peel [] (prn "Peeled an apple"))
(fruit/eat peel)

If you really want to do local capture, you can force it with a ~' (unquote-quote) in the macro:

(ns fruit)
(defmacro eat [] `(~'peel))
Stuart Sierra
This is not quite what I meant. I'd like the `peel` function to be called on expand time. I've clarified the question.
Michiel de Mare
I understand, will provide another answer.
Stuart Sierra
+1  A: 

As explained in the edited question, this is slightly different from local capture, because you're not using peel in the macroexpansion, but rather in the execution of the macro itself.

That's difficult because macros do not evaluate their arguments. Even if you pass peel as an argument to eat, within the body of the macro it's just a symbol, not a callable function.

The only way to do what you want (without using eval) is to resolve the symbol at compile time:

(defmacro eat []
   ((var-get (resolve 'peel)))
   ... return the expansion of "eat" ...)

The resolve function takes a symbol and returns the Var it is mapped to in the current namespace. Once you have the Var, you can retrieve the actual function (the Var's value) with var-get. The extra set of parentheses calls that function.

Needless to say, this is a very unusual design and might warrant rethinking.

Stuart Sierra