Skip to content

Instantly share code, notes, and snippets.

@IGJoshua
Last active January 28, 2022 21:00
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save IGJoshua/0388076fcc9253575ea7b20f4522fa4d to your computer and use it in GitHub Desktop.
Save IGJoshua/0388076fcc9253575ea7b20f4522fa4d to your computer and use it in GitHub Desktop.
Reloadable function vars
(require '[clojure.spec.alpha :as s])
(s/def ::defreloadable-args
(s/cat :name simple-symbol?
:doc (s/? string?)
:attr-map (s/? map?)
:fn-tails (s/+ any?)))
(defmacro defreloadable
"Defines a new function as [[defn]], but old references will refer to new versions when reloaded.
This will construct a phantom var that's used for the lookup, so calls to
functions defined with this macro will have an additional layer of
indirection as compared to normal functions. This should also work in
production environments compiled with direct linking turned on.
I do not recommend using this macro, but it can be useful for beginners
who are learning how to write webservers or other persistent applications
and don't want to worry about having a bad reloadability experience.
Instead of using this, I recommend learning about Clojure's evaluation
model, which will allow you to have the same benefits as using this
macro, but without any magic."
[& args]
(let [args (s/conform ::defreloadable-args args)]
`(let [v# (or (when-let [fn# (binding [*ns* ~*ns*]
(resolve '~(:name args)))]
(-> (meta fn#) ::impl))
(with-local-vars [v# nil] v#))]
(alter-var-root v# (constantly (fn ~@(:fn-tails args))))
(doto (def ~(:name args) (fn [~'& args#] (apply @v# args#)))
(alter-meta! merge (assoc (merge {:doc ~(:doc args)}
~(:attr-map args))
::impl v#))))))
(s/fdef defreloadable
:args ::defreloadable-args)
@IGJoshua
Copy link
Author

Consider this to be under the MIT license, feel free to copy and use in any way you like.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment