Reputation: 1095
I am running through the RabbitMQ tutorials on their web-page and as an exercise am trying to refactor the examples they provide to make them more general and composable. I am stuck on the second "blabbr" example. Here is the function I would like to refactor:
(defn -main
[& args]
(let [conn (rmq/connect)
ch (lch/open conn)
ex "nba.scores"
users ["joe" "aaron" "bob"]]
(le/declare ch ex "fanout" :durable false :auto-delete true)
(doseq [u users]
(start-consumer ch ex u))
(lb/publish ch ex "" "BOS 101, NYK 89" :content-type "text/plain" :type "scores.update")
(lb/publish ch ex "" "ORL 85, ALT 88" :content-type "text/plain" :type "scores.update")
(Thread/sleep 2000)
(rmq/close ch)
(rmq/close conn)))
I thought I could make a macro and call it in the function, like this:
(defmacro wrap-publish [default-exchange-name content mType data]
`(for [datum# data]
(lb/publish ch ex default-exchange-name datum# :content-type ~content :type ~mType)))
(defn -main
[...]
...
(wrap-publish default-exchange-name content-type mType data)
...
When I test the wrap-publish
macro by itself at the repl, however, I get this error:
java.lang.ClassCastException: clojure.lang.Var$Unbound cannot be cast to` com.novemberain.langohr.Channel basic.clj:89 langohr.basic/publish
It seems that there is something global going on that won't let me bind my vars, but I have no idea what. I followed my nose to the source-code thrown at me from the stack-trace, and hit a dead end there. I just don't know what to do. I am a new programmer taking baby steps into the world of async and macros. So I would appreciate any advice that would help me to not only accomplish my goal but also provide general insights that will inform my basic skills and let me know if I am taking the right approach. I am using the langohr dependency [com.novemberain/langohr "2.9.0"].
Upvotes: 1
Views: 120
Reputation: 26446
As to the macro written
It looks like you are missing some unquotes in your macro definition. I don't use that library and you didn't provide an SSCCE, so I am advising without testing.
You macro should likely be
(defmacro wrap-publish [default-exchange-name content mType data]
`(doseq [datum# ~data]
(lb/publish ~'ch ~'ex ~default-exchange-name datum# :content-type ~content :type ~mType)))
Note the added ~' on ch
and ex
and the added ~ on data
and default-exchange-name
. Note also the change from for
to doseq
as for
is lazy.
You would use like this
...
(let [...
ch ...
ex ...
...] ; end let bindings
...
(wrap-publish "" "text/plain" "scores.update" ["BOS 101, NYK 89", "ORL 85, ALT 88"]))
...
Since this produces code
(macroexpand-1 '(wrap-publish "" "text/plain" "scores.update" ["BOS 101, NYK 89", "ORL 85, ALT 88"]))
;=> (clojure.core/doseq [datum__1237__auto__ ["BOS 101, NYK 89" "ORL 85, ALT 88"]]
; (lb/publish ch ex "" datum__1237__auto__ :content-type "text/plain" :type "scores.update"))
containing the symbols ch
and ex
, they must be available in the let
bindings.
As to the advise requested
There is really no good reason to write a macro here. If you are looking for advise, avoid writing macros altogether for your first 6-12 months with Clojure. Focus on functional programming skills first. After that, still avoid writing macros whenever possible!
The code that this macro produces should probably just be the code that you write:
(doseq [d ["BOS 101, NYK 89" "ORL 85, ALT 88"]]
(lb/publish ch ex "" d :content-type "text/plain" :type "scores.update"))
instead of messing with writing your own macros (doseq
is macro itself).
If you need to do similar in multiple places, just define a function. If you have some context (e.g. ch
and ex
) that you don't care to repeat or that needs to escape its lexical scope, create a closure.
Upvotes: 3