1
votes

I have a macro for creating a hash-map. I should send a name of a mashup and contents that get from calling a function (in this example it is a function xx).

(defmacro data-for-mashup [mashup-name func]  
`(map  {} {:title :content} {~mashup-name (~@func)}))  

The macro should get any mashup name and any function, that's why I wanted it to be a macro.

Wnen I call it I want the result to look like this

{:title "Events Mashup"
 :content [{ :event-name "event name 1"
                    :performer "performer 1"
                  :date "date 1"
                       :start-time "start time 1"
                       :end-time "end time 1"}
  {:event-name "event name 2"
                       :performer "performer 2"
                      :date "date 2"
                       :start-time "start time 2"
                       :end-time "end time 2"}]}

The value of content tag is in fact a vector of maps.

A call to a macro should get the result like this

(data-for-mashup "events" (xx))  

in REPL I get this as a result

(["events" #<datamodel$xx mashup_dsl.datamodel$xx@530f0fbd>])  

macroexpand-1 results in this

(macroexpand-1 `(data-for-mashup "events" (xx)))  
(clojure.core/map {} {:title :content} {"events" mashup-dsl.datamodel/xx})  

(The xx is a function in my namespace.)

When in fact I want it to expand to this

(map {} {:title :content} {"events" (xx)})  

What am I doing wrong?

2
You will get better answers if you specify what your output should look like. Also, there is nothing here that indicates this should have to be a macro, and though I have no idea what you are trying to do, it will likely be easier using a function rather than a macro. - noisesmith
@noisesmith I added the brief explanation of what I intend to do. I want to make a macro because I want to be able to call it with any function (xx) in this example. Maybe I got it wrong. - Vesna
Any function can take any other function as an argument. Why do you need the function to be inside parenthesis? Where does the :content keyword end up? Where does the :event-data keyword come from? - noisesmith
@noisesmith yes I see it now, when I checked your answer, I also edited my question and changed that :event-data tag is in fact :content tag. - Vesna

2 Answers

4
votes

First off, are you trying to create a call to map, or a hash-map?

map is for applying its first arg to each element of the other args. Applying {} to something will not make a hash-map. An empty hashmap will return nil for any single argument.

user> (map {} [:a :b :c])
(nil nil nil)

Two arguments provided to an empty hashmap as a function, will always return the second argument. What are you trying to do here?

user> ({} :a 0)
0

user> (map {} [:args :here :are-meaningless] (range))
(0 1 2)

Next, ~@ takes the top level of nesting off of the argument. If you want to return the function inside a list, you need to construct a list around it. quote (also written as ') will prevent a pair of parens from causing application of a function, and instead construct a list.

user> (macroexpand-1 ''(+))
(quote (+))

user> (quote (+))
(+)

user> (defmacro data-for-mashup [mashup-name func]  
        `(map  {} {:title :content} {~mashup-name '(~@func)}))
#'user/data-for-mashup
user> (macroexpand-1 '(data-for-mashup "events" (+)))
(clojure.core/map {} {:title :content} {"events" (quote (+))})
user> (data-for-mashup "events" (+))
(["events" (+)])

This is identical to not using ~@, and just using ~ directly, but quoting what it returns.

user> (defmacro data-for-mashup [mashup-name func]  
        `(map  {} {:title :content} {~mashup-name '~func}))
#'user/data-for-mashup
user> (macroexpand-1 '(data-for-mashup "events" (+)))
(clojure.core/map {} {:title :content} {"events" (quote (+))})
user> (data-for-mashup "events" (+))
(["events" (+)])

If you are trying to construct a hash-map, I think one of the following definitions is likely what you want:

user> (defmacro data-for-mashup [mashup-name func]  
        `(zipmap [:title :content] [~mashup-name '~func]))
#'user/data-for-mashup
user> (data-for-mashup "events" (+))
{:content (+), :title "events"}

user> (defn data-for-mashup [mashup-name val] (zipmap [:title :content] [mashup-name val]))
#'user/data-for-mashup
user> (data-for-mashup "events" (+))
{:content 0, :title "events"}
1
votes

If I've understood your question right, you can use a function here, instead of a macro. I'd strongly recommend using functions whenever you can. They are much more composable and easier to read.

;; This mashup function is a higher-order function
;; It takes *another* function as an argument (f here is your content-getting function)
(defn data-for-mashup [mashup-name f]  
    {:title mashup-name :content (f)})

;; A sample content getting function
(defn events-content []
  [{:event-name "Great concert"}
   {:event-name "Above Average concert"}])

;; Call the data-for-mashup with the title and the content-getting function
(data-for-mashup "events" events-content)

;; Result
{:title "events"
 :content
    [{:event-name "Great concert"}
     {:event-name "Above Average concert"}]}