在 Clojure 中使用非命名空间符号

working with non-namespaced symbols in clojure

这是一个最小的工作示例,展示了 Clojure 如何处理非命名空间符号:

(defmacro simple-macro [s]
  (name `~s))

(str "And the answer is "
     (simple-macro v1))

现在我想做一些更复杂的事情。受此示例启发:

(defn typical-closure []
  (let [names (atom [])]
    (fn [arg] (swap! names conj arg) @names)))

(def Q (typical-closure))
(Q 1)
(Q 2)
;; [1 2]

我现在想定义一个类似的闭包来获取未定义变量的名称。

(defn take-names-fun []
  (let [names (atom [])]
    #((swap! names conj (simple-macro %)) (deref names))))

(def P (take-names-fun))
(P v1)

但这并没有像希望的那样工作;我收到错误:

Unable to resolve symbol: v1 in this context

有没有办法解决这个问题,以便我们可以将名称“v1”添加到上面定义的名称列表中?

我尝试使用宏代替(受“掌握 Clojure 宏”第 21 页上的语法技巧的启发)...但是 this answer 在 ask.clojure.org 上说它没有意义在宏中定义一个原子的闭包。

(defmacro take-names-macro []
  (let [names (atom [])]
    `(fn [~'x] (swap! ~names conj (simple-macro ~'x)) (deref ~names))))

(def R (take-names-macro))

事实上,我在这里遇到了另一个错误:

Can't embed object in code, maybe print-dup not defined:

但是,在defn中使用原子则没有这样的限制。也许在一天结束时我需要将我的符号放在命名空间中...?

因此,fn 是用 clojure 编写的,不幸的是,无法从 fn 主体中获取作为参数传递的 var 的名称。更多关于 clojure src 的经验可能能够更好地解释为什么会这样,我最初的猜测是它与保持线程本地范围的隔离和惰性有关。

但是绝对没有什么能阻止您使用您的闭包思想编写一个包装其他宏的宏!

下面是一个例子,说明如何编写类似的内容:

不太确定您最终要完成的是什么。

但是,由于 P 是一个函数,它会总是 计算它的参数。所以,如果你给它传递一个未定义的符号,你就会得到你得到的错误。相反,您必须创建一个宏,以便您可以引用未定义的符号(以停止对参数的评估),然后将其传递给 P。这是一个这样做的例子。

user> (defn take-names-fun []
        (let [names (atom [])]
          (fn [arg] (swap! names conj (name  arg)))))
#'user/take-names-fun
user> (def P (take-names-fun))
#'user/P
user> (defmacro PM [s] `(P (quote ~s)))
#'user/PM
user> (PM v1)
["v1"]
user> (PM v2)
["v1" "v2"]
user> 

您可能会发现有关 Evaluation in Clojure 的文章有帮助。

@dorab 的回答很好。 但您也可以告诉自己:“将未定义的变量输入函数时,我必须引用它们以避免对它们求值!”

所以,之后:

(defn typical-closure []
  (let [names (atom [])]
    (fn [arg] (swap! names conj arg) @names)))

(def Q (typical-closure))

做:

user=> (Q 'v1)
[v1]
user=> (Q 'v2)
[v1 v2]
user=> (Q 3)
[v1 v2 3]
user=> (Q 'v4)
[v1 v2 3 v4]
user=> 

通过这种方式,您不需要宏,并且可以在求值参数和 not-evaluated 参数(未定义符号)之间交替。