解释
这里的问题是这doc
是一个宏,而不是一个函数。source
您可以使用repl 中的宏来验证这一点。
(source doc)
; (defmacro doc
; "Prints documentation for a var or special form given its name"
; {:added "1.0"}
; [name]
; (if-let [special-name ('{& fn catch try finally try} name)]
; (#'print-doc (#'special-doc special-name))
; (cond
; (special-doc-map name) `(#'print-doc (#'special-doc '~name))
; (resolve name) `(#'print-doc (meta (var ~name)))
; (find-ns name) `(#'print-doc (namespace-doc (find-ns '~name))))))
如果您是 Clojure(和 lisps)的新手,您可能还没有遇到过宏。作为一个毁灭性的简短解释,函数在评估代码上运行,宏在未评估代码上运行- 即源代码本身。
这意味着当您键入
(doc (rand-nth (keys (ns-publics 'clojure.core))))
doc
尝试对实际的代码行进行操作(rand-nth (keys (ns-publics 'clojure.core)))
- 而不是评估结果(返回的符号)。代码只不过是 Clojure 中的列表,这就是错误告诉您无法将列表转换为符号的原因。
解决方案
所以,你真正想做的是评估代码,然后调用doc
结果。我们可以通过编写另一个宏来做到这一点,它首先评估你给它的代码,然后将它传递给doc
.
(defmacro eval-doc
[form]
(let [resulting-symbol (eval form)]
`(doc ~resulting-symbol)))
您可以传递eval-doc
任意形式,它会在将它们传递给doc
. 现在我们可以走了。
(eval-doc (rand-nth (keys (ns-publics 'clojure.core))))
编辑:
虽然上述方法在 repl 中运行良好,但如果您使用提前编译,您会发现它每次都会产生相同的结果。这是因为resulting-symbol
inlet
语句是在编译阶段产生的。提前编译一次意味着这个值被烘焙到 .jar 中。我们真正想做的是将评估推doc
到运行时。所以,让我们重写eval-doc
为一个函数。
(defn eval-doc
[sym]
(eval `(doc ~sym)))
就那么简单。