如何使用宏迭代列表?

时间:2018-02-23 01:56:20

标签: clojure

我试图通过在REPL中调用以下表达式来打印给定命名空间中所有函数的文档:

(doseq
  [f (dir-fn 'clojure.repl)]
  (doc f))

但是,调用此表达式会返回nil ,而不会将文档打印到REPL 。我知道这可能与doc是一个宏有关,但我是一个Clojure新手,并不完全确定如何理解这个问题。

  1. 为什么这个表达式会在不打印文档的情况下返回nil
  2. 如何修改此表达式,以便为给定命名空间中的每个函数打印文档?
  3. 谢谢!

    更新:结合两个提供的答案:

    (defn ns-docs [ns']
      (doseq [[symbol var] (ns-interns ns')]
      (newline)
      (println symbol)
      (print "  ")
      (println (:doc (meta var)))))
    (ns-docs 'clojure.repl)
    

3 个答案:

答案 0 :(得分:2)

相反,我会从这里开始:

请注意doc位于命名空间clojure.repl中,它反映了其预期用途(由repl中的人员执行)。这里有一些代码也将在命名空间上进行迭代。打印doc字符串(使用不同的技术):

  (doseq [[fn-symbol fn-var] (ns-interns 'demo.core)]
    (newline)
    (println fn-symbol)
    (println (:doc (meta fn-var))))

其中demo.core是感兴趣的名称空间。

请注意ns-interns同时为您提供符号和var:

fn-symbol  => <#clojure.lang.Symbol -main>
fn-var     => <#clojure.lang.Var #'demo.core/-main>

meta函数有很多其他信息,您可能希望有一天使用它们:

(meta fn-var)   =>   
<#clojure.lang.PersistentArrayMap 
  { :arglists ([& args]), 
    :doc "The Main Man!", 
    :line 9, :column 1, 
    :file "demo/core.clj", 
    :name -main, 
    :ns #object[clojure.lang.Namespace 0x14c35a06 "demo.core"]}>

答案 1 :(得分:2)

虽然这可能无法帮助您回答问题,但在您学习Clojure时,评估宏的问题会出现很多。

宏负责评估他们的论点。在这种情况下,clojure.repl/doc将忽略当前的词汇上下文,并假设您提供的符号f是您要查看其文档的函数的名称。这样做是因为它打算在REPL中使用,并且假设你不想一直输入引号。

由于f不存在,因此不会输出任何内容。然后doseq会返回nil,因为它只存在于副作用中 - 因此从do开始。为了将参数传递给拒绝遵守这样的词汇上下文的宏,您需要为列表中的每个元素编写代码。

您可以手动执行此操作,也可以将代码构建为数据,并将其传递给eval以执行。您可以使用doseq

以命令式样式执行此操作
(doseq [f (ns-interns 'clojure.repl)]
  (eval `(doc ~(symbol "clojure.repl" (str (first f))))))

或稍微更多的Clojurey方式(这将允许您通过从末尾删除eval并在REPL处运行它来查看它将执行的代码):

(->> (ns-interns 'clojure.repl)
     (map #(list 'clojure.repl/doc (symbol "clojure.repl" (str (first %)))))
     (cons `do)
     eval)

在这两个中,我们使用quote和syntax-quote从命名空间反映的符号列表中构造一些代码,并将其传递给eval以实际执行它。 This page on Clojure's weird characters应该指出你正确的方向来理解这里发生了什么。

这是一个为什么你不应该写宏的例子,除非你没有其他选择。 Macro不会编写,而且通常很难处理。要进行更深入的讨论,Fogus's talkChristophe Grand's talk都是很好的对话。

答案 2 :(得分:1)

  

为什么这个表达式在没有打印文档的情况下返回nil?

因为doc宏从循环中接收符号f,而不是直接接收函数符号。

  

如何修改此表达式,以便打印给定命名空间中每个函数的文档?

(defn ns-docs [ns']
  (let [metas (->> (ns-interns ns') (vals) (map meta) (sort-by :name))]
    (for [m metas :when (:doc m)] ;; you could filter here if you want fns only
      (select-keys m [:name :doc]))))

(ns-docs 'clojure.repl)
=>
({:name apropos,
  :doc "Given a regular expression or stringable thing, return a seq of all
        public definitions in all currently-loaded namespaces that match the
        str-or-pattern."}
 ...
)

然后,您可以根据需要打印这些地图/字符串。