clojureclojurescriptedn

How to serialize ClojureScript functions to edn and then later deserialize and invoke them?


If you construct a string like so

(def s (pr-str {:greet '(partial str "Hello" " " "World!")}))

How do you read the structure using a reader (i.e. read-string) and pull the value for the :greet key back as a function than can be invoked?

Note that by quoting the code it preserves the shape. If I drop the quote it serializes the guts of the underlying javascript function. I tried a backtick (`) too.

The goal is to be able to save off functions that a user constructed in some app, serialize those to edn and then later deserialize that text, pull out functions that are capable of being invoked.

The above bit of code should return "Hello World!" when invoked.

Safety can be addressed separately.


Solution

  • You could make it work by using the same solution http://clojurescript.net/ is using - which is cljs-bootstrap basically.

    Demo:

    I prepared a demo repository you can clone and run. It contains a simple web page with an input you can type into, which will be live evaluated. Code is very short and simple, so it should be easy to follow and adapt to your needs. It looks like this:

    enter image description here

    Solution

    Step 1: Get cljs-bootstrap file compiled to JS. It contains the read_eval_print method we'll use. Load this file in your HTML file before you load your compiled CLJS files.

    Step 2: Since we use a JS, and not a proper CLJS dependency, we might need externs (i.e. in advanced compilation mode):

    var cljs_bootstrap = {};
    cljs_bootstrap.core = {};
    cljs_bootstrap.core.read_eval_print = function() {};
    

    Remember to add them to your project.clj.

    Step 3: read_eval_print accepts two arguments - first is a string with the ClojureScript code, second is the callback that will be called once evaluation completes. This code will do:

    (let [code "(prn ((partial str \"Hello\" \"World\") \" :)\"))"
          cljs (-> js/window .-cljs_bootstrap .-core)]
      (.read_eval_print
        cljs
        code
        (fn [success _] (prn "Success?" success))))
    

    It's pretty straightforward actually, as you can see:

    This code, when executed in the browser, prints that to the JS console:

    enter image description here

    That's pretty much it.

    Few notes