如何在Clojure表达式中找到所有自由变量?
通过自由变量,我指的是在本地环境中未定义的所有符号(包括表达式中定义的所有本地环境),未在全局环境中定义(当前所有符号)命名空间,包括从其他命名空间导入的所有命名空间),而不是Clojure原语。
例如,此表达式中有一个自由变量:
(fn [ub]
(* (rand-int ub) scaling-factor))
那是scaling-factor
。 fn
,*
和rand-int
都在全球环境中定义。 ub
在其发生的范围内定义,因此它也是一个绑定变量(即不是免费的)。
我想我自己也可以写这个 - 它看起来并不太难 - 但我希望有一些标准的方法来实现它我应该使用,或者以标准方式访问Clojure编译器来执行此操作(因为Clojure编译器肯定也必须这样做)。天真实现的一个潜在缺陷是表达式中的所有宏必须完全展开,因为宏可以引入新的自由变量。
答案 0 :(得分:7)
您可以使用tools.analyzer.jvm
分析表单,传递特定的回调以处理无法解析的符号。
像这样:
(require '[clojure.tools.analyzer.jvm :as ana.jvm])
(def free-variables (atom #{}))
(defn save-and-replace-with-nil [_ s _]
(swap! free-variables conj s)
;; replacing unresolved symbol with `nil`
;; in order to keep AST valid
{:op :const
:env {}
:type :nil
:literal? true
:val nil
:form nil
:top-level true
:o-tag nil
:tag nil})
(ana.jvm/analyze
'(fn [ub]
(* (rand-int ub) scaling-factor))
(ana.jvm/empty-env)
{:passes-opts
(assoc ana.jvm/default-passes-opts
:validate/unresolvable-symbol-handler save-and-replace-with-nil)})
(println @free-variables) ;; => #{scaling-factor}
它还将正确处理宏展开:
(defmacro produce-free-var []
`(do unresolved))
(ana.jvm/analyze
'(let [x :foo] (produce-free-var))
(ana.jvm/empty-env)
{:passes-opts
(assoc ana.jvm/default-passes-opts
:validate/unresolvable-symbol-handler save-and-replace-with-nil)})
(println @free-variables) ;; => #{scaling-factor unresolved}