This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-01-16
Channels
- # announcements (2)
- # babashka (51)
- # beginners (165)
- # biff (39)
- # clara (1)
- # clj-kondo (20)
- # cljsrn (6)
- # clojure (64)
- # clojure-belgium (11)
- # clojure-conj (2)
- # clojure-europe (12)
- # clojure-nl (3)
- # clojure-norway (7)
- # clojure-uk (6)
- # clojurescript (11)
- # conf-proposals (1)
- # conjure (1)
- # core-async (19)
- # cursive (6)
- # data-science (16)
- # datomic (6)
- # deps-new (4)
- # fulcro (60)
- # funcool (3)
- # graalvm (9)
- # helix (14)
- # introduce-yourself (4)
- # jobs-discuss (13)
- # joyride (1)
- # kaocha (2)
- # malli (12)
- # off-topic (25)
- # polylith (9)
- # portal (3)
- # practicalli (1)
- # rdf (43)
- # re-frame (7)
- # reagent (5)
- # releases (5)
- # remote-jobs (8)
- # sci (5)
- # shadow-cljs (42)
- # squint (6)
- # xtdb (5)
Hmmm, I thought I understood CLJS macros, but I have a stumper.
• gen.cljc has a function, make-tag
• gen-macro.clj pulls that in fine with
(:require
[tiltontec.mxweb.gen :refer [make-tag]])
• gen-macro.clj then uses that function in a macro-writing macro, carefully qualifying it in expanded code:
(defmacro deftag [tag]
(let [kids (gensym "kids")
vargs (gensym "vargs")
tag-name (gensym "mxweb-name")]
`(defmacro ~tag [& ~vargs]
(let [~tag-name (str '~tag)]
(cond
(nil? ~vargs)
`(tiltontec.mxweb.gen/make-tag ~~tag-name {} {} nil)
...snip other cases similarly qualified...
• gen-macro.cljs in its entirety:
(ns tiltontec.mxweb.gen-macro
(:refer-clojure :exclude [map meta time])
(:require-macros [tiltontec.mxweb.gen-macro]))
The app itself tries to get away with just this require:
[tiltontec.mxweb.gen-macro
:refer-macros [section audio img header form main nav
h1 h2 h3 input footer p a span label ul li div button br]]
but I get:
8 | (defn index []
9 | (div {:class "dashboard-status"}
---------^----------------------------------------------------------------------
Use of undeclared Var tiltontec.mxweb.gen/make-tag
--------------------------------------------------------------------------------
Hmmm. OK, so it knows for what it is looking.
If I add [tiltontec.mxweb.gen :refer [make-tag]]
to the requires, it compiles OK.
So why does not the compiler know how to find make-tag
, an internal function only?
Looking at https://clojurescript.org/guides/ns-forms, I suspect I need sth in gen-macro.cljs that pulls in make-tag
from gen.clj
. There are several examples showing how I could make make-tag
available to the app, but it really is just needed internally, so those examples do not seem pertinent. But hey, worth a shot.
Am I on the right track? :thinking_face:Yep. 🙂
(ns tiltontec.mxweb.gen-macro
(:refer-clojure :exclude [map meta time])
(:require [tiltontec.mxweb.gen :refer [make-tag]])
(:require-macros [tiltontec.mxweb.gen-macro]))
Macros in CLJS are just code substitution.
When you use tiltontec.mxweb.gen/make-tag
in a macro in such a way so that it ends up in the CLJS code, it ends up in there as-is, without the necessary require
. Same happens in CLJ, but there it's fine to load a namespace somewhere and then refer to it by using a qualified symbol in some other place.
BTW because you have that gen-macro.cljs
, you shouldn't need to use :refer-macros
with modern tools. Unless I'm mistaken, a plain :refer
should work.
Another solution BTW is, instead of using macros-writing-macros, to use plain macros that rely on reusable functions.
That, of course, would solve just this problem and wouldn't solve any issue where you still need to refer to some third CLJS namespace in the expanded code.
Yeah, I was trying to remember how refer-macros got simplified. Thx for the reminder.
I need macros because Matrix libraries lean hard on lexical capture. They have a crucial me
variable, akin to this
or self
, that makes the resulting code vastly cleaner.
Agreed in general on the principle, of course.
But you should still be able to extract some code from make-tag
into a function and then use that code in both make-tag
and deftag
. At least, I can't imagine something that would preclude that. At worst, you'd have to pass some extra arguments to that common function explicitly.
make-tag
is a function. "tag" in the HTML sense, by the way.
The macro-writer is deftag
, which arranges for, say, span
to be authored with variable signatures. deftag
has a bit of work to do to offer devs an uncluttered yet unrestricted API:
(defmacro deftag [tag]
(let [kids (gensym "kids")
vargs (gensym "vargs")
tag-name (gensym "mxweb-name")]
`(defmacro ~tag [& ~vargs]
(let [~tag-name (str '~tag)]
(cond
(nil? ~vargs)
`(tiltontec.mxweb.gen/make-tag ~~tag-name {} {} nil)
(map? (first ~vargs))
(cond
(map? (second ~vargs))
`(tiltontec.mxweb.gen/make-tag ~~tag-name ~(first ~vargs) ~(second ~vargs)
~(when-let [~kids (seq (nthrest ~vargs 2))]
`(tiltontec.model.core/cFkids ~@~kids)))
:default `(tiltontec.mxweb.gen/make-tag
~~tag-name ~(first ~vargs)
{}
~(when-let [~kids (seq (nthrest ~vargs 1))]
`(tiltontec.model.core/cFkids ~@~kids))))
:default `(tiltontec.mxweb.gen/make-tag
~~tag-name {} {}
(tiltontec.model.core/cFkids ~@~vargs)))))))
cFkids
is another macro that does quite a bit to make MX syntax so clean.
The punch line, btw, is that mxWeb apps almost never refer to me
explicitly, because it gets set up and consumed transparently by the stack.Oh, alright, I misunderstood then - that "macro writing macro" made me think it was a macro.
The story behind cFkids
is pretty cool. It expands to the more interesting the-kids
:
(defmacro the-kids
"Macro to flatten kids in 'tree' and relate them to 'me' via the *par* dynamic binding"
[& tree]
`(binding [*par* ~'me]
(assert *par*)
(doall (remove nil? (flatten (list ~@tree))))))
An essential quality of Matrix is that a tree of objects "come to life" reactively and smoothly against all odds, a key ingredient being that each object knows its parent at birth. To avoid passing that around all over the place, we use a dynamic var *par*
bound to the anaphor me
, which Cell formulas are responsible for establishing.
All that said, I must confess the JS version of all this did not turn out horrible without macros, but that has classes and define_property
and fat arrow functions: https://tilton.medium.com/simplejx-aweb-un-framework-e9b59c12dcff