【问题标题】:How can I iterate over a list with a macro?如何使用宏迭代列表?
【发布时间】:2018-02-23 01:56:20
【问题描述】:

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

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

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

  1. 为什么这个表达式返回nil而不打印文档?
  2. 如何修改此表达式以打印给定命名空间中每个函数的文档?

谢谢!

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

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

【问题讨论】:

    标签: clojure


    【解决方案1】:

    相反,我会从这里开始:

    注意doc 位于命名空间clojure.repl 中,这反映了它的预期用途(由repl 中的人)。下面是一些代码,它们也将在命名空间上迭代并打印文档字符串(使用不同的技术):

      (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"]}>
    

    【讨论】:

    • 那是一片广阔的海洋......有什么可以帮助解决我的具体问题吗?我假设它与编译时和运行时有关,不确定是否有另一个函数可以用来将文档作为数据返回,或者是一种扩展宏内联的方法。
    【解决方案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 应该为您指明正确的方向,以了解这里发生了什么。

    这是一个为什么你不应该写宏的例子,除非你没有其他选择。宏不构成,并且通常难以使​​用。对于更深入的讨论,Fogus's talkChristophe Grand's talk 都是很好的谈话。

    【讨论】:

    • 谢谢!虽然很有趣,但宏似乎添加了一个可能令人困惑的认知上下文——尤其是对于刚开始使用 Clojure 的开发人员。您的两个示例似乎更多地表达了处理宏所施加的上下文,而不是为命名空间中的函数打印文档(不批评您的答案的质量,只是注意到认知负载宏添加到解决方案中)。再次感谢!
    【解决方案3】:

    为什么这个表达式不打印文档就返回 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."}
     ...
    )
    

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

    【讨论】:

    • 谢谢,这是解决问题的好方法!
    猜你喜欢
    • 1970-01-01
    • 2023-03-28
    • 2020-05-02
    • 1970-01-01
    • 1970-01-01
    • 2015-09-12
    • 2013-03-21
    • 2012-03-20
    • 1970-01-01
    相关资源
    最近更新 更多