1

I have this ns with a macro in it. The annoying thing im dealing with is that the taoensso.timbre macro only works as a variadic expression (timbre/info "a" "b" "c"). A list of items wont log right (timbre/info ["a" "b" "c"]). Im trying to create a wrapper macro that lets the code call (logger/info) in the same variadic form, then process all elements, and then pass to timbre/info

(ns logger
  (:require [taoensso.timbre :as timbre :include-macros true])) ; a third party logger

;; A bit of pseudo code here. If you pass in a vector of args, you should get a vector of args with some changes
(defn scrub [args]
  (if (listy)
     (mapv (fn [a] (scrub args) args)
     (if (is-entry a) {:a "xxx"} a)

(defmacro info
  [& args]
  `(timbre/info ~@(scrub args)))

This doesnt work because scrub is called immediately and wont resolve symbols passed in. I need something like either of these that dont work.

(defmacro info
  [& args]
  `(timbre/info @(scrub-log-pii ~args)))

(defmacro info
  [& args]
  `(timbre/info ~@('scrub-log-pii args)))

My last thought was to try to wrap the timbre macro in a function so the macro and evaluation happen in the right order. There is however, no way to "apply" to a macro.

(defn info3
  [& args]
  (timbre/info (scrub-log-pii (vec args))))

Any ideas?

2 Answers 2

2

not exactly an answer to the question as phrased (macro application stuff), but rather the practical timbre solution, that may be applicable in your specific case:

here you can see that all timbre macros use log! macro, which in turn accepts the collection of args.

so, just implementing your procedure as

(defmacro info* [args] `(log! :info :p ~args ~{:?line (fline &form)}))

should do the trick.

Sign up to request clarification or add additional context in comments.

3 Comments

In particular, the docstring for log! gives this example as well.
I did look at that. fline is a private function so I stopped. Im now realizing that meta data is optional.
I tried again and im still having issues with the arg forms while applying changes. Thanks for the suggestion.
2

You have encountered a problem of using macros known as "turtles all the way down". That is, instead of using function composition, you may need to write a wrapper macro, then another wrapper macro for that, etc.

The detailed steps to writing a macro are described in this answer:

For your specific problem, we could to this:

(ns tst.demo.core
  (:use demo.core tupelo.core tupelo.test)
  (:require
    [clojure.pprint :as pp]))

(defn infix-impl
  [a op b]
  (list op a b))

(defmacro infix
  "Allows user to have Java-style infix operators:

        (infix 2 + 3)
  "
  [a op b] (infix-impl a op b))

(defn infix-seq-impl
  [args]
  `(let [form#   (cons 'infix ~args)
         result# (eval form#)]
     result#))

(defmacro infix-seq
  [args] (infix-seq-impl args))

(dotest
  (is= 5 (infix 2 + 3))

  (let [params '[2 + 3]]
    (pp/pprint (infix-seq-impl 'params))

    (is= 5 (infix-seq params))))

Here we use the infix macro to show how to create a wrapper macro infix-seq that accepts a sequence of params instead of N scalar params. The printed output shows the generated code:

(clojure.core/let
  [form__24889__auto__    (clojure.core/cons 'tst.demo.core/infix params)
   result__24890__auto__  (clojure.core/eval form__24889__auto__)]
 result__24890__auto__)

A more general version

The applied macro below allows you to pass in the name of the macro to be "applied" to the param sequence:


(defn applied-impl
  [f args]
  `(let [form#   (cons ~f ~args)
         result# (eval form#)]
     result#))

(defmacro applied 
  [f args] (applied-impl f args))

(dotest
  (nl)
  (let [params '[2 + 3]]
    ;      (applied 'infix params)   ; using a single quote fails
    (is= 5 (applied `infix params)) ; using a backquote works
    (is= 5 (applied 'tst.demo.core/infix params)) ; can also use fully-qualified symbol with single-quote
    (is= 5 (applied (quote tst.demo.core/infix) params)) ; single-quote is a "reader macro" for (quote ...)
    ))

5 Comments

This apply-macro only works if you have the arguments as separate literals already. There's no reason to write (apply-macro foo [x y z]) when you could instead just write (foo x y z). And it doesn't help with (foo (make-xyz)), because (apply-macro foo (make-xyz)) also can't possibly know how many items are returned by that function call.
Good point; I didn't think it through. Fixed the answer.
Right, eval is required if you're going to do trickery like the question asks for, which is why it's generally not a thing you want to do. Your current solution is probably the closest one can get.
This solution doesnt help since I cant redefine all of the inputs to be literals. In my case, if I need to touch every call to the macro to change to string literal, there are simpler forms I can migrate to. The messy marco stuff is to avoid redefining 2k legacy calls to log.
params is any Clojure sequence (list, vector, seq, lazy-seq, etc). The macro applied has no knowledge of what is in the seq. That why it needs to use eval.

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.