Clojure - a let in a macro won't work - macros

I have created a macro which creates a named dispatcher with 3 associates functions get-dispatcher, set-dispatcher and call-dispatcher to work with the dispatcher (they get a dispatching function, add one or call one). It all works just fine! However, now I want to automate the related functions names creation, thus I am putting all these internals of the macro into a let which defines that simple construction function. Note that in the code below only the get- function's name is constructed with that automation. The set- and call- ones name creation still has that manual smell.
(defmacro create-dispatcher [name]
;creates a set of dispatching functions tagged
`(do
;define dispatcher
(def ~(symbol name) ~(atom {}))
(let
[name-w-prefix (fn [x] (~(symbol (str x "-" name))))]
; -- define getter
(defn (name-w-prefix "get")
"get-dispatcher [tag]: get a dispatcher fn by tag"
(~'[] (println "no tag is provided for '" ~(str name) "' dispatcher"))
(~'[tag]
(do
(println "dispatcher '" ~(str name) "' called with '" ~'tag "' tag")
; return the tagged dispatcher
( (keyword ~'tag) #~(symbol name) )))
)
; -- define caller
(defn ~(symbol (str "call-" name))
"get-dispatcher [tag & args]: call a dispatcher fn by tag and apply to the args"
~'[tag & args]
(apply (~(symbol (str "get-" name)) ~'tag) ~'args)
)
; -- define setter
(defn ~(symbol (str "set-" name))
~'[tag fn]
"add-dispatcher [tag fn]: add a dispatcher fn associated with the tag"
(swap! ~(symbol name) assoc (keyword ~'tag) ~'fn)
)
)
; -- report
(println "created dispatcher set for '" ~(str name) "' ok!")
))
However, there is a problem. The name-w-prefix in the let statement binding causes errors. How can I fix that?
(also any advices on improvement are welcome since I am a newb and that is almost the first thing that I wrote in Clojure)

All symbols in a macro are resolved in the current namespace and expected to evaluate to a var. You could quote the name-w-prefix symbol, but that would risk colliding with symbols passed in to the macro during macro expansion. So, Clojure provides a special syntax for use in syntax-quoted forms for generating symbols - just append a # to the end of the symbol and Clojure will treat that as a quoted, auto-generated symbol. So in this case, replace occurrences of name-w-prefix with name-w-prefix# and you should be good to go.
Taking a step back and looking at what your overall goal is, I think you should move the name-w-prefix definition outside the syntax quotes and then use syntax-escape to call it. Otherwise, you'll get still more errors because defn requires a symbol, so once expanded the macro must produce a defn form that has a symbol as its second item. Something along the lines of:
(defmacro create-dispatcher [name]
(let [name-w-prefix #(symbol (str % "-" name))]
`(do
(def ~(symbol name) (atom {}))
(defn ~(name-w-prefix "get")
([] (println "no tag provided"))
([tag#] (println "called with tag" tag#))))))
Note that I've changed ~'[tag] to [tag#] in the defn body in accordance with what I was talking about above.

Related

How can I add a second parameter to a macro in Clojure?

I have the following Clojure wrapping macro with 1 parameter:
(defmacro with-init-check
"Wraps the given statements with an init check."
[body]
`(if (initialized?)
~body
(throw (IllegalStateException. "GeoIP db not initialized."))))
I want to add ip-version to it so I can check if just :IPv6 or :IPv4 is initialized. However the parameter does not get passed through when I try this:
(defmacro with-init-check
"Wraps the given statements with an init check."
[ip-version body]
`(if (initialized? ip-version)
~body
(throw (IllegalStateException. "GeoIP db not initialized."))))
When I use it like this, I get "no such var" at the "if-let [location..." line:
(defn- lookup-location
"Looks up IP location information."
[ip ip-version]
(with-init-check ip-version
(if-let [location (get-location ip ip-version)]
{:ip ip
:countryCode (.countryCode location)
:countryName (.countryName location)
:region (.region location)
:city (.city location)
:postalCode (.postalCode location)
:latitude (.latitude location)
:longitude (.longitude location)
:dma-code (.dma_code location)
:area-code (.area_code location)
:metro-code (.metro_code location)})))
How can I get the ip-version to the initialized? function?
Unquote it with ~:
(defmacro with-init-check
"Wraps the given statements with an init check."
[ip-version body]
`(if (initialized? ~ip-version) ; unquoted with ~
~body
(throw (IllegalStateException. "GeoIP db not initialized."))))
Deducing from your docstring, you probably also want to allow multi-expression bodies and unquote-splice them in a do expression:
(defmacro with-init-check
"Wraps the given statements with an init check."
[ip-version & body] ; multi-expression bodies with &
`(if (initialized? ~ip-version)
(do ~#body) ; unquote-spliced with ~#
(throw (IllegalStateException. "GeoIP db not initialized."))))

Run a transform on any subform of a form that is a call to a function in clojure.zip

Well, the title is a mouthful, so I will expand on it. I have the following code (it is incomplete, mostly just for illustration):
(use '[clojure.zip :only [up down right node])
(defn in-zip? [form]
(contains? (-> 'clojure.zip ns-publics vals set) (first form)))
(defn do-something-to-zip-form [fx loc rest]
;; this is where I would do the transform, but for now, I will just
;; return the actual form
form)
(defn transform-zip [form]
(if (in-zip? form)
(do-something-to-zip-form form)
form))
(defmacro gozip [body]
(clojure.walk/postwalk transform-zip body))
The purpose of in-zip? is to take a form and determine whether the evaluated form calls to a function in clojure.zip . So, something like (is-zip? '(clojure.zip/down loc) or (is-zip? '(up loc)) should return true, any form that isn't calling a function within clojure.zip should return false.
I want to be able to call gozip with a form, and have every call to a function in clojure.zip be replaced by my do-something-to-zip-form transformation. Some examples:
(gozip (-> loc down right right (clojure.zip/update 3))
In the above expression, I would like it to run the transform in 5 places (loc,down,right,right, and update) because those are all functions within clojure.zip.
(gozip (let [d (down loc)] (node loc)))
In the above expression, I would like to run transform in 2 places (down, node).
Sorry about being so pedantic about explaining what I am interested in, it is just that I am having trouble explaining exactly what I want, seems easier through examples. I am looking to use gozip in clojure and clojurescript code.

Calling a Clojure function with string inside swap?

The macro, transform!, as defined below seems to work for => (transform! ["foo" 1 2 3]). The purpose is to take in a list, with the first element being a string that represents a function in the namespace. Then wrapping everything into swap!.
The problem is that transform! doesn't work for => (transform! coll), where (def coll ["foo" 1 2 3]). I am getting this mystery exception:
#<UnsupportedOperationException java.lang.UnsupportedOperationException: nth not supported on this type: Symbol>
The function:
(defmacro transform!
" Takes string input and update data with corresponding command function.
"
[[f & args]] ;; note double brackets
`(swap! *image* ~(ns-resolve *ns* (symbol f)) ~#args))
I find it strange that it works for one case and not the other.
Macros work at compile-time and operate on code, not on runtime data. In the case of (transform! coll), the macro is being passed a single, unevaluated argument: the symbol coll.
You don't actually need a macro; a regular function will suffice:
(defn transform! [[f & args]]
(apply swap! *image* (resolve (symbol f)) args)))
Resolving vars at runtime could be considered a code smell, so think about whether you really need to do it.
You're passing a symbol to the macro, namely coll. It will try to pull that symbol apart according to the destructuring statement [f & args], which won't be possible of course.
You can also use (resolve symbol) instead of (ns-resolve *ns* symbol).

How to generate arguments of a Clojure macro dynamically?

I am currently developing a small CMS using the wonderful Enlive as templating engine. Enlive has a macro called at that takes a node (a map) specifying the HTML snippet and an arbitrary number of tuples each consisting of a selector (a vector) and a transformation (a closure).
(at a-node
[:a :selector] a-transformation
[:another :selector] another-transformation
...)
Now I want to generate the tuples depending upon incoming data/context. I have tried a lot of different things without success. For example
(let [this (repository/u "http://example.com/ACMECorp")
statements (repository/find-by-subject this)
context {:depth 1}]
`(at (snippet-for 'this 'context)
[root] (set-attr :about (str 'this))
~#(loop [rules []
st statements]
(if-not (seq st)
rules
(recur (conj rules
`[:> (attr= :property ~(str (repository/predicate (first st))))]
`(content (renderit ~(repository/object (first st)) 'context)))
(rest st))))))
Any help is highly appreciated.
-Jochen
Clojure is a Lisp, so you can always fallback to building the code you'd want as a list, and call eval on it. I'm not 100% sure about the code you gave, but I'd guess you just want to enclose your whole syntax-quote in an eval call.
(let [this (repository/u "http://example.com/ACMECorp")
statements (repository/find-by-subject this)
context {:depth 1}]
(eval `(at (snippet-for 'this 'context)
[root] (set-attr :about (str 'this))
~#(loop [rules []
st statements]
(if-not (seq st)
rules
(recur (conj rules
`[:> (attr= :property ~(str (repository/predicate (first st))))]
`(content (renderit ~(repository/object (first st)) 'context)))
(rest st)))))))
Not sure if they are interchangeable, but take a look at the at* function. Seems to me that your problem is at being a macro.
EDIT: They're not. Call it like this:
(at* a-node
[[:a :selector] a-transformation
[:another :selector] another-transformation
...])

build-protocol Clojure macro

As a follow-up to my previous question, I am trying to write a macro that builds a defprotocol:
(build-protocol AProtocol
[(a-method [this]) (b-method [this that])]
(map (fn [name] `(~(symbol (str name "-method")) [~'this ~'that ~'the-other]))
["foo" "bar" "baz"])
(map (fn [name] `(~(symbol (str name "-method")) [~'_]))
["hello" "goodbye"]))
should expand to
(defprotocol AProtocol
(a-method [this])
(b-method [this that])
(foo-method [this that the-other])
(bar-method [this that the-other])
(baz-method [this that the-other])
(hello-fn [_])
(goodbye-fn [_]))
My attempt:
(defmacro build-protocol [name simple & complex]
`(defprotocol ~name ~#simple
~#(loop [complex complex ret []]
(if (seq complex)
(recur (rest complex) (into ret (eval (first complex))))
ret))))
and expansion (macroexpand-1 '(...)):
(clojure.core/defprotocol AProtocol
(a-method [this])
(b-method [this that])
(foo-method [this that the-other])
(bar-method [this that the-other])
(baz-method [this that the-other])
(hello-method [_])
(goodbye-method [_]))
I'm not really happy about the eval. Also, the map expressions are pretty ugly. Is there a better way? Any and all comments welcome.
Once I get this working, I'm going to do a similar macro for (build-reify ...). I'm writing a rather large Swing application and have several components (JButtons, JCheckBoxes, etc.) that have almost identical method signatures and actions.
I think you're doing it upside down. Specify the "-method" stuff first, wrapped in a container of some kind so build-protocol knows what's what, and let it do the map inside the macro. e.g:
(build-protocol AProtocol
{[this that whatever] [foo bar baz],
[_] [hello goodbye]}
; a-method and b-method...
)