Search code examples
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:

    • cljs is used to obtain the read_eval_print method from the window object. That's because we import this file as a plain JS dependency, not a CLJS one (that's also the reason we don't need to (:require) anything.
    • callback accepts two arguments - first is a boolean flag set to true if code is valid and could be evaluated, and false otherwise. Second argument is an error.

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

    enter image description here

    That's pretty much it.

    Few notes

    • that it'd be great to use cljs-bootstrap as a Clojure dependency, either as a Lein dependency, or just by copying required namespaces. I didn't do it for the lack of time. It should work the same, you would just (:require) it. I didn't have time to play with it though.
    • when you load the demo page, it'll show some JS loading errors (files not found). Those can be ignored actually, CLJS compilation still works. I haven't looked into that.