哪些变量影响Clojure函数?

哪些变量影响Clojure函数?,clojure,static-analysis,dynamic-analysis,free-variable,Clojure,Static Analysis,Dynamic Analysis,Free Variable,如何通过编程确定哪些变量可能会影响Clojure中定义的函数的结果 考虑Clojure函数的以下定义: (def ^:dynamic *increment* 3) (defn f [x] (+ x *increment*)) 这是x的函数,也是*increment*的函数(也是clojure.core/+(1);但我不太关心这个)。在为该函数编写测试时,我希望确保我控制所有相关的输入,因此我会这样做: (assert (= (binding [*increment* 3] (f 1)) 4

如何通过编程确定哪些变量可能会影响Clojure中定义的函数的结果

考虑Clojure函数的以下定义:

(def ^:dynamic *increment* 3)
(defn f [x]
  (+ x *increment*))
这是
x
的函数,也是
*increment*
的函数(也是
clojure.core/+
(1);但我不太关心这个)。在为该函数编写测试时,我希望确保我控制所有相关的输入,因此我会这样做:

(assert (= (binding [*increment* 3] (f 1)) 4))
(assert (= (binding [*increment* -1] (f 1)) 0))
(假设
*increment*
是一个配置值,有人可能会合理地更改它;我不希望在这种情况下需要更改此函数的测试。)

我的问题是:
(f1)
的值可以依赖于
*增量*
,但不依赖于任何其他Var,我该如何编写断言?因为我希望有一天有人会重构一些代码,并使函数

(defn f [x]
  (+ x *increment* *additional-increment*))
忽略更新测试,我希望测试失败,即使
*额外增量*
为零

这当然是一个简化的例子——在一个大型系统中,可以有很多动态变量,它们可以通过一长串函数调用来引用。即使
f
调用
g
调用
h
引用变量,该解决方案也需要起作用。如果它不声明
(不带str(prn“foo”)
依赖于
*out*
,那就更好了,但这并不重要。如果正在分析的代码调用
eval
或使用Java互操作,当然所有赌注都是无效的

我可以想到三类解决方案:

  • 从编译器获取信息

    我认为编译器确实会扫描函数定义以获取必要的信息,因为如果我试图引用不存在的变量,它会抛出:

    user=> (defn g [x] (if true x (+ *foobar* x)))
    CompilerException java.lang.RuntimeException: Unable to resolve symbol: *foobar* in this context, compiling:(NO_SOURCE_PATH:24) 
    
    请注意,这发生在编译时,不管是否会执行有问题的代码。因此,编译器应该知道函数可能引用哪些变量,我希望能够访问这些信息

  • 解析源代码并遍历语法树,并记录引用变量的时间

    因为代码就是数据等等。我想这意味着调用
    macroexpand
    并处理每个Clojure原语以及它们采用的每种语法。这看起来非常像一个编译阶段,如果能够调用编译器的各个部分,或者以某种方式将我自己的钩子添加到编译器中,那就太好了

  • 对Var机制进行测试,执行测试并查看访问了哪些Var

    不像其他方法那样完整(如果在测试失败的代码分支中使用Var会怎么样?),但这就足够了。我想我需要重新定义
    def
    来生成类似于Var的东西,但以某种方式记录它的访问



  • (1) 实际上,如果您重新绑定
    +
    ,该特定函数不会改变;但是在Clojure 1.2中,您可以通过使其
    (defn f[x](+x0*increment*)
    绕过该优化,然后您可以享受
    (binding[+-](f3))
    的乐趣。在Culjule 1.3试图重新绑定<代码> +>代码>引发错误。

    < P>关于第一个点,您可以考虑使用库。使用它,您可以很容易地确定表达式中使用了哪些动态变量:

    user> (def ^:dynamic *increment* 3)
    user> (def src '(defn f [x]
                      (+ x *increment*)))
    user> (def env {:ns {:name 'user} :context :eval})
    user> (->> (analyze-one env src) 
               expr-seq 
               (filter (op= :var)) 
               (map :var) 
               (filter (comp :dynamic meta)) 
               set)
    #{#'user/*increment*}
    

    我知道这并不能回答您的问题,但如果只提供两个版本的函数,其中一个版本没有自由变量,而另一个版本使用适当的顶级定义调用第一个版本,不是会少很多工作吗

    例如:

    (def ^:dynamic *increment* 3)
    (defn f
      ([x]
         (f x *increment*))
      ([x y]
         (+ x y)))
    

    通过这种方式,您可以针对
    (f x y)
    (不依赖于任何全局状态)编写所有测试。

    是的,或者在架构的一个层中有单元可测试的纯函数,并在其上构建一个(希望很小)层,以将纯函数连接在一起并向它们提供全局值。我的问题更多的是关于如何检查globals不会意外潜入。这看起来很有用,我来看看。我想如果它能做到这一点,那么遍历函数调用树以从调用的函数中收集动态引用应该相当容易?我认为这是可能的。您必须找到定义Var的源代码并进行分析。我有我自己的,我目前正在玩,所以我有兴趣知道这是如何为你工作。非常欢迎提供经验报告:)。库中存在一些问题,这些问题可能是问题,也可能不是问题。一个是它与1.3编译器绑定,可能不适用于任何其他版本的Clojure。