This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-10-03
Channels
- # babashka (5)
- # beginners (34)
- # biff (3)
- # calva (29)
- # cherry (11)
- # cider (7)
- # clojure (148)
- # clojure-brasil (1)
- # clojure-europe (16)
- # clojure-nl (1)
- # clojure-norway (6)
- # clojure-uk (6)
- # clojuredesign-podcast (8)
- # clojurescript (49)
- # cursive (1)
- # datalevin (7)
- # fulcro (1)
- # honeysql (1)
- # jobs (1)
- # matrix (7)
- # off-topic (13)
- # re-frame (12)
- # react (21)
- # reagent (42)
- # releases (6)
- # remote-jobs (2)
- # shadow-cljs (9)
- # solo-full-stack (5)
- # sql (7)
- # squint (9)
- # vim (2)
- # xtdb (11)
- # yamlscript (5)
I get a Use of undeclared Var cljs.core/destructure
that I don’t understand how to get rid off. I’m updating a macro library, https://github.com/PEZ/taplet, that uses cljs.core/destructure
. When bumping/cleaning up the library dependencies, I notice that it used to work in ClojureScript only because of som transient dependencies. I can require the namespace without getting warnings, and it works. shadow-cljs runs the tests fine, and I can run the tests in the repl. All without any warnings. But evaluating the namespace file I get that warning. As long as I can trust that the library will work for any users, I am good to deploy it, but it worries me that the user might get warnings. Here’s the new branch I’m working on, in case someone wants to take a look at what I might be doing wrong. https://github.com/PEZ/taplet/tree/next
A thing I noticed is that to avoid warnings from when requiring the namespace, I need to keep the reference to cljs.core/destructure
in a macro.
(let [taps (->taps label bindings #?(:cljs cljs.core/destructure
:clj destructure))]
...)
If I reference from a utility function (`->taps`) that the macro uses, the require also emits warnings.I only get the warnings when evaluating the namespace using the ClojureScript REPL. With the Clojure REPL, no warnings.
Why is it in a .cljc
file in the first place? Why not .clj
given that its whole API is just macros?
It’s mostly because of Calva making it easy to evaluate things in either the Clojure or the ClojureScript REPL if the code is in a .cljc
file. Means I can exercise the API from Rich comment forms in either REPL.
And how does it work for regular projects that define macros in CLJ files that are intended to be used from CLJS?
Well, you're saying that Calva makes some things easy when everything is in a .cljc
file.
But people that write macros for CLJS usually use two kinds of files - .cljs
where just the CLJS code resides and .clj
where just the macros reside (since they're written in CLJ).
In the latter case, are there any issues with Calva, are any things less easy than in the former case?
Calva uses the Clojure REPL for .clj
files and the ClojureScript REPL for .cljs
files, so that is convenient.
Alright, so what exactly drives the need to have Taplet be implemented in .cljc
, given that it's only macros, that are CLJ anyway (unless you're targeting self-hosted CLJS, but that's a beast on its own)?
I meant that for someone structuring things with .clj
and .cljs
files, Calva doesn’t get in the way. But for me, I like the convenience of using the .cljc
Calva support. It’s also convenience in other aspects. In the case of the taplet.cljc
file, it makes it straightforward to select different destructure
implementations for the different environments.
Is this choice causing the warning, you mean? To me it seems I would get the same warning even if I split it up in two files?
> it makes it straightforward to select different destructure
implementations for the different environments.
AFAICT, the implementation is the same. The only reason CLJS has its own destructure
is exactly for the self-hosted ability.
You can place the whole Taplet into a CLJ file and it'll work flawlessly. The only thing you should add then is a CLJS file that only does :require-macros
on itself.
Maybe I forgot in the OP to mention that I want Clojure users of the macro to not have to include a dependency to ClojureScript. I think that happens transitively with the current version of the library. (But I’m not sure, this is not things I know very much about.)
When I tried using just destructure
for both, things blew up. But maybe that was something else. I’ll try that now.
All you need is two files, one CLJ with all the code you have right now, written only with CLJ in mind, and one CLJS with only (ns ... (:require-macros [...]))
, that's it.
Since I would lose the Rich comment convenience in Calva (or have to duplicate the Rich comment in the two files), I rather keep it as .cljc
.
However,
> When I tried using just destructure
for both, things blew up.
This was not really so. Now trying it again I get reminded what happened. I get different warning from the ClojureScript REPL:
> Use of undeclared Var pez.taplet/destructure
Both versions of the warning worries me. But this one worries me more. 😃
> That’s the same as using a .cljc
file and a reader conditional, right?
In the end - yes. But CLJC gives you much more hassle with reader conditionals and a chance to accidentally use any vars in CLJS outside of macros. Not relevant for Taplet, but anything that can't be DCE'ed from the CLJC file will increase the size of the release JS bundle.
> Use of undeclared Var pez.taplet/destructure
Wrap the defn
in a reader conditional.
This whole thread is 10% of the reasons for why writing macros in CLJC is not a good idea. :)
Haha, point taken. But wrapping the defn
in a reader conditional helped. To summarize the answer to my question in OP:
> Evaluating destructure
in the ClojureScript REPL causes the warning. And it is unnecessary to evaluate it in ClojureScript anyway.
Something like that?
It's not evaluating that, it's defining a function that refers to destructure
.
The analyzer reads the source code of the defined function and tries to understand where destructure
comes from. It can't find it, so it complains.
Thanks! 🙏 And why did it not happen when requiring the namespace? I think you have left enough clues for me to understand this, but I’m a bit too stupid about this to quite understand.
Are you sure you required that namespace?
$ echo "(ns x) (defn f [] (g))" > x.cljs
$ clj -Sdeps '{:paths ["."] :deps {org.clojure/clojurescript {:mvn/version "RELEASE"}}}' -M -m cljs.repl.node
ClojureScript 1.11.121
cljs.user=> (require 'x)
WARNING: x is a single segment namespace at line 1 /home/p-himik/Downloads/x.cljs
WARNING: Use of undeclared Var x/g at line 1 /home/p-himik/Downloads/x.cljs
nil
cljs.user=>
uhm these reader conditionals do nothing, it'll always take the :clj
branch when loading macros. as they are loaded in clojure. it would only have an effect for self-host, which you said you don't target.
thats why I agree that this should not be a .cljc file, it just makes things unnecessarily complicated
The reader conditionals are only needed here because pez prefers to load that file in a CLJS REPL to use rich comments.
but regardless, when expanding for CLJS you should really be using the cljs.core/destructure not the CLJ one
Huh? So macros that are intended to be used from CLJS should use cljs.core/destructure
?
What if a macro targets both CLJ and CLJS?
What are the relevant differences between the two versions of destructure
that make this necessary?
cljs.core/destructure
generates slightly different code better targetted for CLJS. clojure.core/destructure
also generates code that CLJS won't understand?
at least a quick glance suggests so? https://github.com/clojure/clojure/blob/master/src/clj/clojure/core.clj#L4463-L4464 I never actually tried
Oh crap, you're right. Unfortunate indeed, especially given that there's no standard way to determine where a macro is being used.
Yeah, but I've always seen it a happenstance and not something deliberate. So not "standard". :)
I very much doubt this is ever going to change, too many things use it at this point
Of course. It's just one of those know-how things and I simply don't like it, that's all.
Thanks, both of you. ❤️ 🙏 Now I’ve updated the next version and have no warnings anywhere, while still using destructure
from cljs.core
in the ClojureScript environment. It does get a bit ridiculous with all reader conditionals, but now at least I know more about the trade-offs, and also about your strong opinions on the matter.
I've read up on the web components story in CLJS. I found https://github.com/WICG/webcomponents/issues/587 that describes an ES5-compatible workaround for web components. So I created https://gist.github.com/bhlieberman/3ad68a9c0ad939c7e70f410323a52af1 that does work, and I am curious what other pitfalls there would be to doing this, apart from the really gross boilerplate involved (and the hot reload thing)?