I'm by no means a compiler expert so I don't know if the name "lifting" is even in the right ballpark for this. Kinda hard to google when you don't know the term you are looking for.
I'm writing a pure ClojureScript implementation to do DOM processing. Kinda like a mix between Svelte and React. In it I have a macro that rewrites a common Hiccup form to create a "create" function and an "update" function. For this macro to work efficiently I want to be able to create ns-level "vars".
An example of what this may look like
(defn card [{:keys [title body]}]
(<> [:div.card
[:div.card-title title]
[:div.card-body body]
[:div.card-footer
[:div.card-actions
[:button "ok"]
[:button "cancel"]]]]))
So the <>
macro (called a "fragment") rewrites this to two functions. One that creates the initial DOM and one that updates the parts that can actually change. This is done to avoid as much "diffing" as possible.
Without going into too much more detail on this it basically generates
(defn card [{:keys [title body]}]
(fragment-create "dummy-id" [title body]
(fn create-fn [state] ...)
(fn update-fn [state] ...)))
The problem with this is that it will re-create the functions each time the card
function is called. Since functions do not have "identity" in JS the runtime can't tell if that is still the same identical?
fragment from before or if it was updated (either via the REPL or hot-reload). It has to rely on some unique identifier for that. The JS runtime would also always allocate the functions on each call although the runtime may decide to re-use the previous fragment and throw them away immediately.
So what I'd like to be able to generate is something closer to this from the original code
(def fragment-123
(fragment-create
(fn create-fn [state] ...)
(fn update-fn [state] ...)))
(defn card [{:keys [title body]}]
(fragment-use fragment-123 [title body]))
So the fragment "handler" is only created once (currently using a deftype but could just be a map). And it is then "used" whenever the card
function is called.
Without "lifting" support the code can only generate the def
inline
(defn card [{:keys [title body]}]
(do (def fragment-123
(fragment-create
(fn create-fn [state] ...)
(fn update-fn [state] ...)))
(fragment-use fragment-123 [title body])))
That is obviously problematic. So it would need to check if the fragment is already defined and so on. reify
currently does this and the code really isn't pleasent to look at.
I implemented this as an experiment in shadow-cljs where macros wanting to use this can call a special function (from &env
currently, could be a binding) that basically lets them "prepend" a form to whatever top-level form it is currently working on.
Should this be something the compiler natively supports?
I know that Clojure doesn't have it either but it is sort of doing it by creating classes and accessing them whenever so it kinda does this.
The implementation could be different. I'm just trying to determine if I'm crazy for wanting to do this in the first place. The previous implementation worked fine just ended up being more code with a bunch of extra additional checks since it can't rely on identical?
.