Skip to content

Instantly share code, notes, and snippets.

@viebel
Forked from mfikes/README.md
Last active July 30, 2020 17:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save viebel/42af99337d27fd862a112389314a124b to your computer and use it in GitHub Desktop.
Save viebel/42af99337d27fd862a112389314a124b to your computer and use it in GitHub Desktop.

Self-hosted:

$ plk
ClojureScript 1.10.597
cljs.user=> (require '[foo.core :refer [defnmy]])
nil
cljs.user=> (defnmy FOO clojure.string/lower-case [x]
       #_=>   (inc x))
#'cljs.user/foo
cljs.user=> (foo 1)
2

Regular:

$ clj -A:cljs/rel -re node -r
ClojureScript 1.10.753
cljs.user=> (require '[foo.core :refer [defnmy]])
nil
cljs.user=> (defnmy FOO clojure.string/lower-case [x]
  (inc x))
#'cljs.user/foo
cljs.user=> (foo 1)
2

For the self-hosted case copy enough of the Planck code that bottoms out in the use of eval to pull it off. An issue might arise if the code referenced in the function itself needs to be loaded; then there would be asynchronous issues to cope with.

This all surrounds whether the namespace for the function symbol passed to defmy has been required or not. If not, then resolve is going to return nil. The default implementation of eval for self-hosted environments is assuming the result of the evaluation is available synchronously. (See https://github.com/clojure/clojurescript/blob/master/src/main/cljs/cljs/js.cljs#L1238) But if you evaluate a require form, then it is up to each self-hosted environment to implement that. In Planck's case require does indeed have a synchronous implementation.

(Actually it used to be the case that require needed to be directly implemented by a self-hosted environment, but at some point a macro was introduced, but nevertheless the root issue remains regarding whether cljs.js/load-fn is asynchronous.)

Broadly speaking, you want to write a macro that, upon macroexpansion, makes use of a function that may or may not have been loaded at that time. And in self-hosted ClojureScript, the namespace loading mechanism is fundamentally asynchronous.

If your use case knows a priori that the function symbols being passed to defmy are already loaded, then resolve should be sufficient for that use case.

No magic. ^ In the above, resolve is your custom implementation, not cljs.core/resolve

(ns foo.core
#?(:cljs (:require [planck.core :refer [requiring-resolve]])))
(defmacro defnmy [name fn args & body]
(let [n (symbol ((requiring-resolve fn) (str name)))]
`(defn ~n ~args ~@body)))
(ns foo.core
(:require-macros foo.core))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment