我写了一个宏来将函数定义包装在一些有用的日志中:
(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#)))
这适用于没有文档字符串的函数:
(defn-logged foo
[x]
(* 2 x))
(foo 3)
; INFO - foo input: 3
; INFO - foo output: 6
; 6
但是如果对于带有文档字符串的函数失败则非常糟糕:
(defn-logged bar
"bar doubles its input"
[x]
(* 2 x))
; IllegalArgumentException Parameter declaration clojure.tools.logging/info should be a vector
如何使用和不使用文档字符串使我的宏工作?
答案 0 :(得分:4)
一种方法是查看传递给defn-logged
的参数。如果名称后面的第一个是字符串,则将其用作doc字符串,否则将doc留空:
(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#))))
使用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
不使用文档字符串进行测试:
(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
它仍然不是defn
的完全等价物,至少因为defn
支持在地图,阅读器宏和字符串中传递的元数据。但它适用于doc字符串。
答案 1 :(得分:1)
我定义了一个用于识别此场景中参数的通用函数。
(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)]))
然后你的宏定义变得如此简单:
(defmacro my-macro
[fn-name & args]
(let [[params body docstring] (resolve-params args)]
~your-macro-here))