开发者

Can clojure be made fully dynamic?

In clojure 1.1, all calls were dynamic, meaning that you could redefine a function at the REPL and it would be included in the running program automatically. This was also nice for things like dotrace.

In clojure 1.2, many calls seem to be statically linked, and if I want to replace a function, Sometimes, I have to find all the places where it's called and put #' in front of them.

Worse, I can't predict where I'll need to do this.

Is it possible to go back to the old default of dynamic linking? Maybe if you needed the extra iota of speed you could switch it back on for the production app, but for development I much prefer the 1.1 behaviour.

I'm hoping for some sort of compiler option like *warn-on-reflection*.

Edit:

I'm confused about what's going on. More specifically, here are two functions. I prefer the behaviour of the second. How can I make the first one behave like the second, as I believe it used to do in 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

Edit (to the whole question, and a change of title):

Joost points out below that what's actually going on here is that the self call in factorial is being optimized away. I can't see why that would be done, since you can't do that many recursive self calls without blowing the stack, but it explains the observed behaviour. Perhaps it's开发者_如何转开发 something to do with anonymous self-calls.

The original reason for my question was that I was trying to write http://www.learningclojure.com/2011/03/hello-web-dynamic-compojure-web.html, and I got irritated with the number of places i had to type #' to get the behaviour I expected. That and the dotrace made me think that the general dynamic behaviour had gone and that the on the fly redefining, which works in some places, must be done with some clever hack.

In retrospect that seems a strange conclusion to jump to, but now I'm just confused (which is better!). Are there any references for all this? I'd love to have a general theory of when this will work and when it won't.


Everything in Clojure is fully dynamic, but you have to take note of when you're working with a Var and when you're working with the Function which is the current value of that Var.

In your first example:

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

The factorial symbol is resolved to the Var #'user/factorial, which is then evaluated by the compiler to get its current value, a compiled function. This evaluation happens only once, when the function is compiled. The factorial in this first example is the value of the Var #'user/factorial at the moment the function was defined.

In the second example:

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

You have explicitly asked for the Var #'user/factorial. Invoking a Var has the same effect as dereferencing the Var and invoking its (function) value. This example could be written more explicitly as:

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

The clojure.contrib.trace/dotrace macro (which I wrote, ages ago) uses binding to temporarily rebind a Var to a different value. It does not change the definition of any functions. Instead, it creates a new function which calls the original function and prints the trace lines, then it binds that function to the Var.

In your first example, since the original function was compiled with the value of the factorial function, dotrace has no effect. In the second example, each invocation of the factorial function looks up the current value of the #'user/factorial Var, so each invocation sees the alternate binding created by dotrace.


So that people don't confused about the issues, I'm explaining the "problem" related to web development.

This is a limitation of Ring not Clojure (and really it's a limitation of the Java Jetty library). You can always redefine functions normally. However the handler given to the Jetty server process cannot be redefined. Your functions are being updated, but the Jetty server cannot see these updates. Providing a var as the handler is the work around in this case.

But note the var is not the real handler. An AbstractHandler must be given to the Jetty server, so Ring uses proxy to create one which closes over your handler. This is why in order for the handler to updated dynamically it needs to be var and not a fn.


I think you're mistaken. In clojure 1.2 you certainly can redefine functions and calling code will call the new definitions. In 1.3 it looks like this might change somewhat, but 1.3 is not fixed at all yet.


In Clojure-1.3 you will also be able to redefine functions at runtime (thus changing the root binding) this will stil work the same as 1.2 and 1.1. you will however need to mark variables that will be dynamically rebound with the binding as dynamic. This breaking change offers

  • significant speed improvements
  • allows bindings to work through pmap
  • totally worth it because 99% of vars are never rebound anyway
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜