如何在Clojure表达式中找到所有自由变量?

时间:2018-01-31 01:23:29

标签: clojure scope macros

如何在Clojure表达式中找到所有自由变量?

通过自由变量,我指的是在本地环境中未定义的所有符号(包括表达式中定义的所有本地环境),未在全局环境中定义(当前所有符号)命名空间,包括从其他命名空间导入的所有命名空间),而不是Clojure原语。

例如,此表达式中有一个自由变量:

(fn [ub]
  (* (rand-int ub) scaling-factor))

那是scaling-factorfn*rand-int都在全球环境中定义。 ub在其发生的范围内定义,因此它也是一个绑定变量(即不是免费的)。

我想我自己也可以写这个 - 它看起来并不太难 - 但我希望有一些标准的方法来实现它我应该使用,或者以标准方式访问Clojure编译器来执行此操作(因为Clojure编译器肯定也必须这样做)。天真实现的一个潜在缺陷是表达式中的所有宏必须完全展开,因为宏可以引入新的自由变量。

1 个答案:

答案 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}