如何为defn-like Clojure宏添加docstring支持?

时间:2014-07-22 21:04:50

标签: macros clojure

我写了一个宏来将函数定义包装在一些有用的日志中:

(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

如何使用和不使用文档字符串使我的宏工作?

2 个答案:

答案 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))