Clojure namespace management - Is there a way to save and restore the state of clojure repl namespaces, imports etc.?

StackOverflow https://stackoverflow.com/questions/3480377

Question

Clojure has a large number functions/macros for working with namespaces and java package imports. To my (limited) understanding the set up of namespaces can be considered state in a clojure process (repl).

When working iteratively at a REPL session, especially when source files are (re)-loaded, I can find it easy to get confused - often when I make a mistake or syntax error in namespace configuration. Other times I want to try out refactoring namespaces/aliases/reference filters but can't easily back out of existing namespace state without restarting the REPL.

For example I would like to be able to checkpoint namespace configuration - such as after the main body of code is loaded at the repl - then get back to that "clean-slate" after trying out some library imported at the REPL so that I can immediately test a source file that imports a filtered subset of methods in that library as part of the ns macro.

Can people recommend ways to save and restore namespace configuration?

Was it helpful?

Solution

I'm sure there's something wrong with this, as I just wrote it in answer to this question, but I see myself using this in my projects, for sure. Just :import it (have it in its own file in your project) and use it liberally.

(ns world)


(defn save-world
  []
  (let [syms (filter identity (distinct (for [i (ns-map *ns*)] (first i))))]
    (for [i syms]
      (vector i
              (ns-resolve *ns* i)))))

(defn destroy-world-but
  [saved]
  (let [syms (filter identity (distinct (for [i (ns-map *ns*)] (first i))))]
    (for [i syms]
      (if-not (or (= (ns-resolve *ns* i) (ns-resolve *ns* saved))
                  (= (ns-resolve *ns* i) (ns-resolve *ns* 'restore-world))
                  (= (ns-resolve *ns* i) (ns-resolve *ns* '*ns*)))
        (ns-unmap *ns* i)))))

(defn restore-world
  [saved]
  (clojure.core/map
   #(intern *ns* (clojure.core/first %) (clojure.core/second %))
   saved))

First, save a the state of your world (the one you want to go back to) like this:

(def *save* (save-world))

Then do whatever you want–experiment. When you're ready to go back to your former state:

(destroy-world-but '*save*)
(restore-world *save*)

And you should be good to go!

(Hope this works! Was working for me–please let me know if there's a problem. I'm sure there's a better way to do this, too, but this works and it's how far I got tonight. I'm sure I'll revise.)

OTHER TIPS

This won't always work. You can remove Vars from a namespace with ns-unmap, but other pieces of code may still hold references to those definitions.

Clojure, because it is based on the JVM, has no concept of a "memory image" like some Common Lisp or Scheme implementations.

DMTCP might do the job in a clumsy way. Google on DMTCP: Distributed MultiThreaded CheckPointing. I use it to checkpoint interactive OCaml programs.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top