1

Basically I'm rather new to macros and I'm trying to work out how to write macros in Clojure. The problem is I keep getting exception errors and it's very difficult to figure out where to proceed. So really what I'm wondering is if I can get a list of either methods (or heuristics) for debugging Clojure macros.

Take the current problem I am working on, I have this model:

{:users [:name :email :registered-on]
 :post [:title :author]}

and I want to convert it into the form:

(do (def new-form-users (cashew.core/new-form "/users/new" "Create a new Users"
         ["name" "email" "registered-on"] ("Name" "Email" "Registered-on"))) 
    (def new-form-post (cashew.core/new-form "/post/new" "Create a new Post"
         ["title" "author"] ("Title" "Author"))))

for which I have written this macro:

(defmacro gen-create-forms [model]
               `(do
                 ~@(for [[entity-kw values] model]
                        (let [entity-sym (-> entity-kw name capitalize)
                             fields (vec (map name values))]
                          `(def ~(symbol (str "new-form-" (name entity-kw))) (new-form ~(str "/" (name entity-kw) "/new") ~(str "Create a new " entity-sym) ~fields ~(map capitalize fields)))))))

However when I run the macro I get:

java.lang.String cannot be cast to clojure.lang.IFn
  [Thrown class java.lang.ClassCastException]

I've tried calling macroexpand-1, but I get the same error leaving me with little idea on how to solve it.

This tutorial provided by John Lawrence Aspden where he says "When the compiler sees a macro, which is just a function that returns some code, it runs the function, and substitutes the code that is returned into the program." prompted me to write the macro as a function which takes in the values I have and outputs the result that I want them to transform into.

Thus this works:

(defn gen-create-forms [model]
               `(do
                 ~@(for [[entity-kw values] model]
                        (let [entity-sym (-> entity-kw name capitalize)
                             fields (vec (map name values))]
                          `(def ~(symbol (str "new-form-" (name entity-kw))) (new-form ~(str "/" (name entity-kw) "/new") ~(str "Create a new " entity-sym) ~fields ~(map capitalize fields)))))))

(gen-create-forms {:users [:name :email :registered-on]
              :post [:title :author]})
(do (def new-form-users (cashew.core/new-form "/users/new" "Create a new Users" ["name" "email" "registered-on"] ("Name" "Email" "Registered-on"))) (def new-form-post (cashew.core/new-form "/post/new" "Create a new Post" ["title" "author"] ("Title" "Author"))))

I'm not sure if I am using macros correctly here or if macros are the right strategy for solving this problem. However some ideas about what you do when faced with exceptions when writing a macro or good techniques for debugging them would be much appreciated.

EDIT: It has been brought to my attention by mikera that this is not a good example, however my question still stands. So to reiterate what techniques would you use when faced with an exception if coding a macro in clojure?

1 Answer 1

2

Personally I wouldn't use a macro for this - my proposed alternative would be:

  • Avoid trying to generate new symbol names in the namespace - this can get messy and complicated!
  • Instead create a single data structure called "new-forms" that contains all the forms in a hashmap. You could use either keywords or strings as keys, personally I'd use keywords like ":users" since you are already using that approach for your model data structure.
  • Generate "new-forms" with a function that takes the model as a parameter and calls (cashew.core/new-form ... ) for each form in the model as required
  • When you want to acess a specific form, you can then just do (new-forms :users) or similar to read the appropriate form out of the hashmap.
Sign up to request clarification or add additional context in comments.

3 Comments

So a nested datastructure that would contain all the functions that I'm defining? ie: (def newform :users {:new #<new-users-fn> :read #<read-users-fn>} :post {:new #<new-post-fn> :read #<read-post-fn>})? How is this different/what advantage does it provide from just creating a new namespace for these generated forms? I will however concede the point that I've used a terrible example, but it still doesn't answer my question. Not that I mind too much, this is still quite useful!
Yep - I think a nested data structure is best. I tend to use namespaces only for "stuff that I manually define in the source code". So anything that I create/generate programatically I would normally put in a data structure. This is just personal preference really (namespaces are actually a kind of map under the hood!), but I find it is helpful to avoid confusion, maintenance headaches and possible conflicts in the namespace.
you know @mikera I'm really beginning to appreciate this comment, Thanks a lot for that idea.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.