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

Reply via email to