Need help with writing macros for ClojureScript

For instance I have a macros in my-macros CLJ namespace (just a let macros copied from the clojure core) :

(ns my-macros)

(defmacro alt-let
	[bindings & body]
  (assert-args
    (vector? bindings) "a vector for its binding"
    (even? (count bindings)) "an even number of forms in binding vector")
  `(let* ~(destructure bindings) ~@body))

I also have my-macros CLJS namespace that includes only:

(ns my-macros
  (:require-macros [my-macros]))

This macros works fine when I use it in my clojurescript code this way:

(ns my-namespace
   (:require [my-macros :refer-macros [alt-let]])) 

(alt-let [[a b] [1 2]]
  (js/console.log (+ a b)))

But if I try to do associative destructuring I get following errors:

(alt-let [{:keys [a b]} {:a 1 :b 2}]
  (js/console.log (+ a b)))
No such namespace: clojure.lang.PersistentHashMap, could not locate clojure/lang/PersistentHashMap.cljs, clojure/lang/PersistentHashMap.cljc, or JavaScript source providing "clojure.lang.PersistentHashMap"

Use of undeclared Var clojure.lang.PersistentHashMap/create

Why do these errors occur and how to overcome this issue?

looks strange, where did the identifier create come from?

You are using the Clojure macro variants of let* and destructure since the macros run in Clojure and everything defaults to using clojure.core. Instead use the CLJS variant via cljs.core/destructure in the macro and everything should be fine.

I’ve tried rewriting CLJ namespace to CLJC like this:

(ns my-macros
  (:require #?(:cljs [cljs.core :as cljs]))

#?(:cljs 
    (cljs/defmacro alt-let
      [bindings & body]
      (assert-args
        (vector? bindings) "a vector for its binding"
        (even? (count bindings)) "an even number of forms in binding vector")
      `(cljs/let* ~(cljs/destructure bindings) ~@body))
   :clj
    (defmacro alt-let
      [bindings & body]
      (assert-args
        (vector? bindings) "a vector for its binding"
        (even? (count bindings)) "an even number of forms in binding vector")
      `(let* ~(destructure bindings) ~@body))

but I still get the same errors.

This is not how macros work. Don’t use CLJC unless you really understand how macros work. I wrote a post explaining macros step by step, maybe that helps.

This should be about all you need (in a .clj) file.

(ns my-macros)

(defmacro alt-let
	[bindings & body]
  (assert-args
    (vector? bindings) "a vector for its binding"
    (even? (count bindings)) "an even number of forms in binding vector")
  `(let* ~(cljs.core/destructure bindings) ~@body))

This doesn’t work too, I get the same errors. By the way, how is cljs/core namespace supposed to work if I want to use this macros in pure clojure? This is the reason I’ve tried to put this macros in a CLJC namespace.

If you want to write the macro in CLJC then the macro must live in the :clj portion of the code and the macro must check whether it is supposed to emit CLJS code or CLJ code. Using a reader-conditional to put a macro in :cljs does not work.

#?(:clj
   (defmacro a-thing [& args]
     (if (:ns &env) ;; this only exists when expanding CLJS code
       `(cljs-code)
       `(clj-code))))
1 Like

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.