On Wed, Nov 17, 2010 at 9:57 PM, Alyssa Kwan <alyssa.c.k...@gmail.com> wrote: > Hi Alex, > > OK, I agree. Dynamic vars are not the same as traditional dynamic > scoping. > > For what I'm doing (making functions durable), it raises the > question: If you persist a function that points to a var, restart the > JVM, and deserialize/load the function from a data store, what should > happen?
How are you planning to persist a function? The clojure reader can't read functions output with spit or println. I can think of at least three ways to build a framework for persisting functions but none run into problems with vars. If the functions are expressible in terms of parameters and a finite set of codes/algorithms that use the parameters together with the function arguments, you can persist just the parameters; for example, (defn make-my-fn [foo bar] [{:foo foo :bar bar} (fn [baz quux] (do-stuff-with foo bar baz quux))]) (defn call-my-fn [f baz quux] ((second f) baz quux)) (defn save-my-fn [f file] (spit file (first f))) (defn load-my-fn [file] (let [parm-map (with-open ... blah blah ... file ... blah blah ...)]) (make-my-fn (:foo parm-map) (:bar parm-map))) If the functions are more varied and the code is different every time/arbitrary, you need something more sophisticated. Option 2 is to turn to the Dark Side and use eval: (defn make-fn* (arg-vec & body) [[arg-vec body] (eval `(fn ~arg-vec ~...@body))]) (defmacro make-fn (arg-vec & body) (make-my-fn* ~arg-vec (quote ~...@body))) (defn call-fn [f & args] (apply (second f) args)) (defn save-fn [f file] (spit file (first f))) (defn load-fn [file] (let [[arg-vec body] (with-open ... blah blah ... file ... blah blah ...)]) (apply make-fn* arg-vec body)) This of course runs into the permgen-leak problem mentioned in another thread recently. A third option avoids evil eval and the permgen leak but won't run the custom functions nearly as fast at runtime and is much, MUCH more work to implement. It probably makes security easier, though (e.g. avoiding infectability of your app by macro viruses like MSWord is vulnerable to). That option is to implement an interpreter for a custom language whose code is written and read. It may again use sexps (and that will save you from having to write a parser -- Clojure's reader will do that job handily for you); the structure will probably be similar to make-fn etc. directly above, but with call-fn invoking the interpreter on the function source and the "function" object being just the source, not a vector of the source and a compiled function. You might also create a compiled representation (e.g. bytecode) and interpret that instead of sexps in call-fn; make-fn* would compile a sexp to bytecode and the "function" object would be a vector of bytecode. The bytecode or directly-interpreted source would also be what got saved and loaded from disk (so load-fn would no longer go through make-fn*, and in the directly-interpreted case, make-fn* and make-fn would no longer exist but you'd probably want call-fn to become call-fn* and have a call-fn macro that quotes the second argument for you). The var binding issue is not present in any of the three cases. Closures would bind the appropriate vars where they appeared in your project's source code and there'd be no mucking about with vars when loading saved parameters to recreate a closure with particular closed-over parameter values. Eval would see whatever vars are visible in the environment in which eval runs. Unfortunately, (doc eval) doesn't have much detail, and in particular doesn't say what environment eval sees, though it doesn't seem to include local variables in the eval's lexical surrounds: user=> (let [x 1] (eval 'x)) #<CompilerException java.lang.Exception: Unable to resolve symbol: x in this context (NO_SOURCE_FILE:10)> It does however seem to see global variables in the *current* namespace when eval is called: user=> (def x 1) #'user/x user=> (eval 'x) 1 user=> (ns goober) nil goober=> (def x 2) #'goober/x goober=> (defn foo [] (eval 'x)) #'goober/foo goober=> (foo) 2 goober=> (ns user) nil user=> x 1 user=> (goober/foo) 1 Note that foo returns 1 when called from in user and 2 when called from in goober. This can be fixed with syntax quote: goober=> (defn foo [] (eval `x)) #'goober/foo goober=> (foo) 2 goober=> (ns user) nil user=> x 1 user=> (goober/foo) 2 or by putting (def goober-ns *ns*) in goober and putting a (binding [*ns* goober-ns] ... ) around the call to eval to force the eval to occur in the goober namespace. Finally, the interpreter option allows full freedom: any of your application's vars can be made visible or not within the interpreter, under whatever names, and each one may be made read-only or redefinable as you see fit. (If any are redefinable with the changes needing to be visible outside the interpreter, then the interpreter needs to have an instruction that results in a set! call.) Again, though, the interpreter is considerably more work and will run the custom functions slower than the other two options. -- You received this message because you are subscribed to the Google Groups "Clojure" group. To post to this group, send email to clojure@googlegroups.com Note that posts from new members are moderated - please be patient with your first post. To unsubscribe from this group, send email to clojure+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/clojure?hl=en