可以使clojure充满活力吗?

时间:2011-03-22 22:19:59

标签: clojure

在clojure 1.1中,所有调用都是动态的,这意味着您可以在REPL中重新定义一个函数,它将自动包含在正在运行的程序中。对于像dotrace这样的东西也很好。

在clojure 1.2中,许多调用似乎是静态链接的,如果我想替换一个函数,有时候,我必须找到调用它的所有地方并将#'放在它们前面。

更糟糕的是,我无法预测我需要做什么。

是否可以返回动态链接的旧默认值?也许如果你需要额外的速度iota,你可以重新开启生产应用程序,但对于开发我更喜欢1.1的行为。

我希望有某种编译器选项,比如* warn-on-reflection *。

编辑:

我对发生的事感到困惑。更具体地说,这里有两个功能。 我更喜欢第二种的行为。我怎么能让第一个表现得像第二个,我相信它曾经在1.1中做过?

user> (clojure-version)
"1.2.0"

user> (defn factorial[n] (if (< n 2) n (* n (factorial (dec n)))))
#'user/factorial

user> (require 'clojure.contrib.trace)
user> (clojure.contrib.trace/dotrace (factorial) (factorial 10))
TRACE t1670: (factorial 10)
TRACE t1670: => 3628800

user> (defn factorial[n] (if (< n 2) n (* n (#'factorial (dec n)))))
#'user/factorial
user> (clojure.contrib.trace/dotrace (factorial) (factorial 10))
TRACE t1681: (factorial 10)
TRACE t1682: |    (factorial 9)
TRACE t1683: |    |    (factorial 8)
TRACE t1684: |    |    |    (factorial 7)
TRACE t1685: |    |    |    |    (factorial 6)
TRACE t1686: |    |    |    |    |    (factorial 5)
TRACE t1687: |    |    |    |    |    |    (factorial 4)
TRACE t1688: |    |    |    |    |    |    |    (factorial 3)
TRACE t1689: |    |    |    |    |    |    |    |    (factorial 2)
TRACE t1690: |    |    |    |    |    |    |    |    |    (factorial 1)
TRACE t1690: |    |    |    |    |    |    |    |    |    => 1
TRACE t1689: |    |    |    |    |    |    |    |    => 2
TRACE t1688: |    |    |    |    |    |    |    => 6
TRACE t1687: |    |    |    |    |    |    => 24
TRACE t1686: |    |    |    |    |    => 120
TRACE t1685: |    |    |    |    => 720
TRACE t1684: |    |    |    => 5040
TRACE t1683: |    |    => 40320
TRACE t1682: |    => 362880
TRACE t1681: => 3628800
3628800

编辑(对整个问题和标题的更改):

Joost在下面指出,这里实际发生的是因子的自我调用正在被优化掉。我不明白为什么会这样做,因为你不能在没有吹掉堆栈的情况下做那么多递归的自我调用,但它解释了观察到的行为。也许这与匿名自我调用有关。

我的问题的最初原因是我试图写http://www.learningclojure.com/2011/03/hello-web-dynamic-compojure-web.html,我对我必须输入#'以获得我预期的行为的地方数量感到恼火。那和dotrace让我觉得一般的动态行为已经消失了,在某些地方工作的动态重新定义必须用一些聪明的黑客来完成。

回想起来,这似乎是一个奇怪的结论,但现在我只是困惑(哪个更好!)。这有什么参考吗?我希望有一个关于什么时候会起作用以及何时起作用的一般理论。

4 个答案:

答案 0 :(得分:11)

Clojure中的所有内容都是完全动态的,但您必须注意何时使用 Var 以及何时使用函数 Var的当前

在你的第一个例子中:

(defn factorial [n] (if (< n 2) n (* n (factorial (dec n)))))

factorial符号被解析为Var #'user/factorial,然后编译器对其进行评估以获取其当前值,即编译函数。编译函数时,此评估仅发生一次。第一个示例中的factorial是定义函数时Var #'user/factorial

在第二个例子中:

(defn factorial [n] (if (< n 2) n (* n (#'factorial (dec n)))))

您已明确要求Var #'user/factorial。调用Var与取消引用Var并调用其(函数)值具有相同的效果。此示例可以更明确地编写为:

(defn factorial [n] (if (< n 2) n (* n ((deref (var factorial)) (dec n)))))

clojure.contrib.trace/dotrace宏(我在很久以前写过)使用binding暂时将Var重新绑定到不同的值。它不会改变任何函数的定义。相反,它创建一个新函数,调用原始函数并打印跟踪线,然后将该函数绑定到Var。

在第一个示例中,由于原始函数是使用factorial函数的编译的,因此dotrace无效。在第二个示例中,factorial函数的每次调用都会查找#'user/factorial Var的当前值,因此每次调用都会看到由dotrace创建的备用绑定。

答案 1 :(得分:9)

因此人们不会对这些问题感到困惑,我正在解释与Web开发相关的“问题”。

这是Ring not Clojure的限制(实际上它是Java Jetty库的限制)。您始终可以正常重新定义功能。但是,无法重新定义为Jetty服务器进程提供的处理程序。您的功能正在更新,但Jetty服务器无法看到这些更新。在这种情况下,将var作为处理程序提供解决方法。

但请注意var不是真正的处理程序。必须为Jetty服务器提供AbstractHandler,因此Ring使用代理创建一个关闭处理程序的服务器。这就是为了使处理程序动态更新的原因,它需要是var而不是fn。

答案 2 :(得分:8)

我觉得你错了。在clojure 1.2中,您肯定可以重新定义函数,调用代码将调用新定义。在1.3中看起来这可能会有所改变,但1.3还没有固定。

答案 3 :(得分:8)

在Clojure-1.3中,您还能够在运行时重新定义函数(从而更改根绑定),这将与1.2和1.1相同。但是,您需要将binding动态反弹的变量标记为动态。这个突破性的变化提供了

  • 显着的速度提升
  • 允许绑定通过pmap
  • 工作
  • 完全值得,因为99%的vars永远不会反弹