Clojure macro to process multiple function metadata - macros

In Clojure, how do I make a library macro which processes supplied functions metadata and return some result? Amount of functions is unlimited and they should be passed without being boxed into a sequence ((my-macro fn1 fn2) instead of (my-macro [fn1 fn2]))
Say, we expect function vars having :foo keys in meta and the macro concatenates their values. The following snippet should work in REPL (considering my-macro is in the namespace):
user=> (defn my-func-1 {:foo "bar"} [])
(defn my-func-1 {:foo "bar"} [])
#'user/my-func-1
user=> (defn my-func-2 {:foo "baz"} [])
(defn my-func-2 {:foo "baz"} [])
#'user/my-func-2
user=> (my-macro my-func-1 my-func2)
(my-macro my-func-1 my-func2)
"barbaz"
I tried several approaches but was only able to process single function so far.
Thanks!

Try this:
(defmacro my-macro [& fns]
`(clojure.string/join (list ~#(map (fn [x] `(:foo (meta (var ~x)))) fns))))
(defn ^{:foo "bar"} my-func-1 [])
(defn ^{:foo "baz"} my-func-2 [])
(my-macro my-func-1 my-func-2) ;; => "barbaz"
How it Works
If you expand the macro you can start to see the parts in play.
(macroexpand '(my-macro my-func-1 my-func-2))
(clojure.string/join
(clojure.core/list (:foo (clojure.core/meta (var my-func-1)))
(:foo (clojure.core/meta (var my-func-2)))))
(var my-func-1)
Function metadata is stored on the var, so using (meta my-func-1) is not sufficient. But, var is a special form and does not compose like a normal function.
(fn [x] `(:foo (meta (var ~x))))
This anonymous function exists inside an escaped form, so it is processed inside the macro to produce the output forms. Internally it will create a the (:foo (meta (var my-func-1))) form by first backtick escaping the outer form to declare it a literal, and not evaluated, list and then unescaping the x var with a tilde to output the value instead of the symbol.
`(clojure.string/join (list ~#(map (fn [x] `(:foo (meta (var ~x))))
fns)))
This entire form is backtick escaped, so it will be returned literally. But, I still need to evaluate the map function generating the (:foo (meta (var my-func-1))) form. In this case I have unescaped, and spliced (#) the result of, the map form directly. This first evaluates the map function and returns a list of generated forms, and then takes the contents of that list and splices it into the parent list.
(defmacro test1 [x] `(~x))
(defmacro test2 [x] `(~#x))
(macroexpand '(test1 (1 2 3))) ;; => ((1 2 3))
(macroexpand '(test2 (1 2 3))) ;; => (1 2 3)
You could also split out the map function in a let statement before hand for slightly more readability.
(defmacro my-macro [& fns]
(let [metacalls (map (fn [x] `(:foo (meta (var ~x)))) fns)]
`(clojure.string/join (list ~#metacalls))))

Related

How can macro variable capture happen with a gensym symbol?

I'm learning common lisp. I have written a version of the once-only macro, which suffers from an unusual variable capture problem.
My macro is this:
(defmacro my-once-only (names &body body)
(let ((syms (mapcar #'(lambda (x) (gensym))
names)))
``(let (,,#(mapcar #'(lambda (sym name) ``(,',sym ,,name))
syms names))
,(let (,#(mapcar #'(lambda (name sym) `(,name ',sym))
names syms))
,#body))))
The canonical version of only-once is this:
(defmacro once-only ((&rest names) &body body)
(let ((gensyms (loop for n in names collect (gensym))))
`(let (,#(loop for g in gensyms collect `(,g (gensym))))
`(let (,,#(loop for g in gensyms for n in names collect ``(,,g ,,n)))
,(let (,#(loop for n in names for g in gensyms collect `(,n ,g)))
,#body)))))
The difference, as far as I can tell, is that the canonical version generates new symbols for every expansion of the macro using only-once. For example:
CL-USER> (macroexpand-1 '(once-only (foo) foo))
(LET ((#:G824 (GENSYM)))
`(LET (,`(,#:G824 ,FOO))
,(LET ((FOO #:G824))
FOO)))
T
CL-USER> (macroexpand-1 '(my-once-only (foo) foo))
`(LET (,`(,'#:G825 ,FOO))
,(LET ((FOO '#:G825))
FOO))
T
The variable my macro uses to store the value of foo is the same for every expansion of this form, in this case it would be #:G825. This is akin to defining a macro like the following:
(defmacro identity-except-for-bar (foo)
`(let ((bar 2))
,foo))
This macro captures bar, and this capture manifests when bar is passed to it, like so:
CL-USER> (let ((bar 1))
(identity-except-for-bar bar))
2
However, I cannot think of any way to pass #:G825 to a macro that uses my-only-once so that it breaks like this, because the symbols gensym returns are unique, and I cannot create a second copy of it outside of the macro. I assume that capturing it is unwanted, otherwise the canonical version wouldn't bother adding the additional layer of gensym. How could capturing a symbol like #:G826 be a problem? Please provide an example where this capture manifests.
We can demonstrate a behavioral difference between my-once-only and once-only:
Let's store our test form in a variable.
(defvar *form* '(lexalias a 0 (lexalias b (1+ a) (list a b))))
This test form exercises a macro called lexalias, which we will define in two ways. First with once-only:
(defmacro lexalias (var value &body body)
(once-only (value)
`(symbol-macrolet ((,var ,value))
,#body)))
(eval *form*) -> (0 1)
Then with my-once-only:
(defmacro lexalias (var value &body body)
(my-once-only (value)
`(symbol-macrolet ((,var ,value))
,#body)))
(eval *form*) -> (1 1)
Oops! The problem is that under my-once-only, both a and b end up being symbol-macrolet aliases for exactly the same gensym; the returned expression (list a b) ends up being something like (list #:g0025 #:g0025).
If you're writing a macro-writing helper that implements once-only evaluation, you have no idea how the symbol is going to be used by the code which calls the macro, whose author uses your once-only tool. There are two big unknowns: the nature of the macro and of its use.
As you can see, if you don't make fresh gensyms, it will not work correctly in all conceivable scenarios.

How to write a Clojure macro that mimics Ruby's %w operator

I'm trying to write my first ever macro in Clojure. I want to mimic Ruby's %w{} operator, which works like this:
irb(main):001:0> %w{one two three}
=> ["one", "two", "three"]
I want to write a function similar in Clojure that returns a vector of words. Here is how it would look:
user=> (%w one two three)
=> ["one" "two" "three"]
I know this is something that cannot be defined as an ordinary function because the symbols would be evaluated before applying and we would see something like this:
user=> (%w one two three)
CompilerException java.lang.RuntimeException: Unable to resolve symbol: one in this context, compiling:(NO_SOURCE_PATH:1:1)
Here is my attempt at a macro:
(defmacro %w [& words]
(map str (vec words)))
But it doesn't work.
user=> (%w one two three)
ClassCastException java.lang.String cannot be cast to clojure.lang.IFn user/eval801 (NO_SOURCE_FILE:1)
Why is this happening?
ANSWERS
So the problem was that the macro actually returned the correct output, but then the repl tried to evaluate it and "one" is not a valid function.
Thanks to the answers below, here are two correct macros that solve this problem:
(defmacro %w-vec [& words]
"returns a vector of word strings"
(mapv str (vec words)))
(defmacro %w-list [& words]
"returns a list of word strings"
(cons 'list (map str words)))
It does not work because after macro-expansion clojure tries to evaluate ("one" "two" "three"), inducing your error message
user=> (%w one two three)
ClassCastException java.lang.String ("one") cannot be cast to clojure.lang.IFn (interface for callable stuff) user/eval801 (NO_SOURCE_FILE:1)
now you could do that
(defmacro %w [& words]
(mapv str (vec words)))
generating a vector
or that
(defmacro %w [& words]
(cons 'list (mapv str (vec words))))
generating (list "one" "two" "three")
or with syntax quote
(defmacro %w [& words]
`(list ~#(map str (vec words))))
Macros can be thought of as regular functions that take unevaluated code (as data structures) and return new code (as data), which then gets evaluated.
Your macro is returning ("one" "two" "three"), which will evaluate as a call of the function "one" with arguments "two" "three".
Straightforward solutions would be to make your macro return either (list "one" "two" "three") or a vector ["one" "two" "three"].

Clojure macro inside another macro: how to generate string from symbol

Probably the title is not 100% correct, but let me show you the issue:
(defmacro nvp!
[n width height]
`(q/defsketch (symbol (str (name '~n) "-viewport" ))
:title (name '~n))
In short: there's a macro called defsketch (it's part of the lib quil, but that does not matter). It's signature is basically defsketch [applet-name & options], and it creates something and binds it to a var called applet-name. For some reason, I want to wrap this into another macro that -- among other things -- takes an applet-name parameter, extends that name into applet-name-viewport and passes that to defsketch. I, however, am unable to figure out how to do that correctly (using macroexpand on the code above ((pprint (macroexpand(nvp test-name 500 500))), I get
(def(clojure.core/symbol (clojure.core/str (clojure.core/name 'my-namespace.core/test-name) "-viewport"))
(quil.applet/applet
:title (clojure.core/name 'my-namespace.core/test-name)))
(clojure.core/symbol (clojure.core/str (clojure.core/name 'my-namespace.core/test-name) "-viewport") -- this part looks good, but it should be evaluated somehow before passing it to the inner macro...
You need to unquote the form that generates the new symbol instead of the original symbol itself.
Here is a small example of how to accomplish this using a defn like macro for the inner macro:
(defmacro mydefn
[name & body]
`(defn ~name ~#body))
(defmacro defnview
[n & body]
`(mydefn ~(symbol (str (name n) "-viewport")) ~#body))
;; OR
(defmacro defnview
[n & body]
(let [n (symbol (str (name n) "-viewport"))]
`(mydefn ~n ~#body)))
Example:
(defnview foo [a] a)
;; => #'user/foo-viewport
(foo-viewport 1)
;; => 1
(macroexpand '(defnview foo [a] a))
;; => (def foo-viewport (clojure.core/fn ([a] a)))

Creating a record with macro

I have a record (defrecord Rec [id])
I work with it like
(def my ( Rec. 2 ))
(println (:id my))
Now I want to replace record def with macro. So that I could write just
(r 2)
(println (:id my))
I wrote macro
(defmacro r [id]
(list 'def 'my (symbol "(") 'Rec. id (symbol ")")))
I checked it with macroexpand
(macroexpand-1 '(r 2)) => (def my ( Rec. 2 ))
But I get RuntimeException: Too many arguments to def on (r 2).
Creating a symbol out of a left paren is not the same as eval-ing text with a left paren. No special significance is attached to the former; the latter causes the reader to produce a nested list which is then evaluated.
In other words, Clojure evaluates data structures, not text (or a list of symbols). When you type something in the REPL, that text is read into a data structure, then the data structure is evaluated.
For this to work correctly, the macro needs to produce a nested list itself:
(defmacro r [id]
(list 'def 'my (list 'Rec. id)))
Or better yet, use the syntax quote operator:
(defmacro r [id]
`(def my (Rec. ~id)))
For illustrative purposes, you can see what happens when Clojure code is read as text:
(read-string "(def my (Rec. 2))")
=> (def my (Rec. 2))
(map type (read-string "(def my (Rec. 2))"))
=> (clojure.lang.Symbol clojure.lang.Symbol clojure.lang.PersistentList)

Dynamic let form as part of reify within a macro

Ok, let's try to get this straight: my final intent is to provide a macro as an API to users which will look like:
(defscript [a b]
(println a))
The result has to be an instance of a Script protocol, which looks like:
(defprotocol Script
(run [this model]))
The idea being that the first argument to defscript is a list of symbols that needs to be bound to correspondent keys in the model:
(.run (defscript [a b] (println a)) {:a 1}) ;; yields 1
I can't come up with any code that can effectively produce such effect, as I'm constantly hitting a wall when trying to use the model parameter, since at macro expansion time it's just a symbol:
(defmacro invoke-
[params model body]
(let [p (flatten (map (fn [x] [x (model (keyword x))]) params))]
`(let [~#p]
~body)))
(defmacro defscript
[params & body]
`(reify Script
(run [~'this ~'model]
(invoke- ~params ~'model ~#body))))
invoke- works fine if called directly:
(invoke- [a] {:a 1} (println a)) ;; prints 1
but it doesn't work when used within defscript as model can't be expanded correctly:
(.run (defscript [a] (println a)) {:a 1}) ;; prints nil
How can I get past this point and glue the pieces together?
It seems that basically, your argument vector is a shortcut for a destructuring binding:
(defscript [a b] body) -> (reify Script (run [this {:keys [a b]}] body))
That way, model is destructured at run time, as it should be.