0
votes

I am trying to define a macro like this:

(defmacro foo-macro
  "[name] is the name of the function to define
   [meta-data] is the map that defines the meta-data of the function"
  [name meta-data]
  (let [vals (gensym "label-")]
    `(def ~name
       ^@~meta-data
       (fn [~vals] (eval nil)))))

However, I am getting this compilation error:

Unhandled java.lang.IllegalArgumentException Metadata must be Symbol,Keyword,String or Map

I think this is normal. However, I am not sure how I can make such a macro to work, or if it is even possible.

Note: I do not want to use with-meta. Read the revision of the resolution of this other question to understand why: Clojure: issues getting the symbol of a function in a looping context.

The only reason why I would use with-meta, is if you have a way to make it return a non-AFunc identifier.

1
What is the macro for? Maybe there's another way to accomplish what you need, can you be more specific about the use case? - Diego Basch
@DiegoBasch What I need is a macro that creates a function. Then I want to attach meta-data to that function object (and not its Var). However, if I use with-meta instead of ^{} then I get #< clojure.lang.AFunction$1@15ab1764> as a reference for that object instead of something more human readable like #<core$foo user.core$foo@66e37466>. Even if the documentation says that there is no difference between these two methods, the difference appears to be that what is returned by with-meta is not the same as with the ^{} macro (not the same object reference) - Neoasimov
@DiegoBasch tell me if it is clear, otherwise I will try once more by updating the question itself. - Neoasimov
You have to have one of the items in the error after ^ because ^ is a reader macro. You could do something like ^{~meta-key ~meta-val}, or use a string and get {:tag "something"}. My question was more about why do you need to generate your functions with a macro, as there is nothing in that particular function that you'd need a macro for. - Diego Basch
I am not sure ^{~meta-key ~meta-val} would work since the meta-data parameter is a map itself (which is used to provide the meta-data to the function. What I need to do is to let people create a series of functions, but with a specific pattern. So I was using a macro to enforce that specific pattern to follow. My problem is that I let them define all the necessary meta-data using the map. However, I am thinking that I could use keyword parameters for the common and more static meta-data values, and then an optional meta-data parameter where they could add more if necessary. - Neoasimov

1 Answers

0
votes

I'm sorry, but you do want to use with-meta, you just want to use it within your macro rather than within the code you return.

The form that you get by reading ^{:foo :bar} (fn []) is approximately the same as what you get by evaluating (with-meta `(fn [])), so that's what you want to do in your macro: evaluate the with-meta call while generating code.

(defmacro foo-macro
  "[name] is the name of the function to define
   [meta-data] is the map that defines the meta-data of the function"
  [name meta-data]
  (let [vals (gensym "label-")]
    `(def ~name
       ~(with-meta `(fn [~vals] (eval nil))
          meta-data))))

Calling (foo-macro fn-name {:foo "bar"}) should give you what you want: a function with (meta fn-name) returning {:foo "bar"} which, when printed, comes out something like #<user$fn_name user$fn_name@35df85a1>.