Reputation: 1071
I wrote a macro to wrap a function definition in some helpful logging:
(defmacro defn-logged
"Wraps functions in logging of input and output"
[fn-name args & body]
`(defn ~fn-name ~args
(log/info '~fn-name "input:" ~@args)
(let [return# (do ~@body)]
(log/info '~fn-name "output:" return#)
return#)))
This works great for functions without docstrings:
(defn-logged foo
[x]
(* 2 x))
(foo 3)
; INFO - foo input: 3
; INFO - foo output: 6
; 6
But if fails terribly for functions with docstrings:
(defn-logged bar
"bar doubles its input"
[x]
(* 2 x))
; IllegalArgumentException Parameter declaration clojure.tools.logging/info should be a vector
How do I make my macro work for functions both with and without docstrings?
Upvotes: 3
Views: 875
Reputation: 20267
I defined a generic function for identifying the parameters in this scenario.
(defn resolve-params
"Takes parameters to a def* macro, allowing an optional docstring by sorting
out which parameter is which.
Returns the params, body, and docstring it found."
[args]
(if (string? (first args))
[(second args) (drop 2 args) (first args)]
[(first args) (rest args)]))
Then your macro definition becomes as simple as this:
(defmacro my-macro
[fn-name & args]
(let [[params body docstring] (resolve-params args)]
~your-macro-here))
Upvotes: 1
Reputation: 54015
One way to do it is to look at the arguments passed to defn-logged
. If the first one after the name is a string use that as doc string, otherwise leave doc empty:
(defmacro defn-logged
"Wraps functions in logging of input and output"
[fn-name & stuff]
(let [has-doc (string? (first stuff))
doc-string (if has-doc (first stuff))
[args & body] (if has-doc (rest stuff) stuff)]
`(defn ~fn-name {:doc ~doc-string} ~args
(println '~fn-name "input:" ~@args)
(let [return# (do ~@body)]
(println '~fn-name "output:" return#)
return#))))
Test with doc string:
(defn-logged my-plus "My plus documented" [x y] (+ x y))
(doc my-plus)
; -------------------------
; user/my-plus
; ([x y])
; My plus documented
; nil
(my-plus 2 3)
; my-plus input: 2 3
; my-plus output: 5
; 5
Test without doc string:
(defn-logged my-mult [x y] (* x y))
(doc my-mult)
; -------------------------
; user/my-mult
; ([x y])
; nil
; nil
(my-plus 2 3)
; my-mult input: 2 3
; my-mult output: 6
; 6
It still is not a complete equivalent of defn
, at least because defn
supports metadata passed in map, reader macro and string. But it works for doc strings.
Upvotes: 5