Ben*_*itz 5 macros scope clojure
如何在Clojure表达式中找到所有自由变量?
通过自由变量,我指的是在本地环境中未定义的所有符号(包括表达式中定义的所有本地环境),未在全局环境中定义(当前命名空间中的所有符号,包括从其他命名空间导入的所有符号) ,而不是Clojure原语.
例如,此表达式中有一个自由变量:
(fn [ub]
(* (rand-int ub) scaling-factor))
Run Code Online (Sandbox Code Playgroud)
那就是scaling-factor.fn,*和rand-int,都在全球环境中定义.ub在它发生的范围内定义,因此它也是一个绑定变量(即不是自由的).
我想我自己也可以写这个 - 它看起来并不太难 - 但我希望有一些标准的方法来做我应该使用的,或者是一种标准的方式来访问Clojure编译器来做到这一点(因为Clojure)编译器肯定也必须这样做).天真实现的一个潜在缺陷是表达式中的所有宏必须完全展开,因为宏可以引入新的自由变量.
您可以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}
Run Code Online (Sandbox Code Playgroud)
它还将正确处理宏展开:
(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}
Run Code Online (Sandbox Code Playgroud)