This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2018-11-10
Channels
- # announcements (1)
- # beginners (2)
- # calva (41)
- # cider (3)
- # cljdoc (2)
- # cljs-dev (23)
- # clojure (94)
- # clojure-dev (23)
- # clojure-russia (5)
- # clojure-spec (9)
- # clojure-uk (85)
- # clojurescript (94)
- # code-reviews (1)
- # cursive (5)
- # datomic (1)
- # emacs (8)
- # figwheel (1)
- # figwheel-main (10)
- # fulcro (27)
- # graphql (11)
- # hyperfiddle (1)
- # jobs-discuss (10)
- # kaocha (3)
- # luminus (7)
- # lumo (1)
- # off-topic (85)
- # onyx (1)
- # pedestal (1)
- # re-frame (3)
- # shadow-cljs (21)
- # tools-deps (1)
- # yada (6)
How does one fix a macro like this? https://gist.github.com/borkdude/145d9433d9e8cc386d40495849b0b3c8
@borkdude bear with me here
does it work if you add a (def foo 42)
in repro.macros
?
I think you need your macros to be in clj
files
cljc files work just fine
workaround looks something like:
(defmacro with-instrument-disabled [& body]
`(choose-env :clj
(clojure.spec.test.alpha/with-instrument-disabled ~@body)
:cljs
(cljs.spec.test.alpha/with-instrument-disabled ~@body)))
oh right
where choose-env is:
(defmacro choose-env [& {:keys [cljs clj]}]
(if (contains? &env '&env)
`(if (:ns ~'&env) ~cljs ~clj)
(if #?(:clj (:ns &env) :cljs true)
cljs
clj)))
(the case macro from macrovich)well there’s no runtime clojure.spec.test.alpha
in CLJS
@borkdude An alternative is to rely on ClojureScript runtime clojure.*
namespace aliasing, and require macro clients to require clojure.spec.test.alpha
via that name. It is not perfect, but by making that concession, it leads to simpler code and uniform usage. https://gist.github.com/mfikes/cef096f0b44018014d9939c67f668ec1
@mfikes thanks. do you have an intuition what is the common way of handling this in other related libs?
I haven't looked at the source for libs that attempt to do this.
But, I do feel that sometimes people try to get away with a single *.cljc
file (for good reasons), but that may make things more complex.
if I put these macros in a .clj file, then I would have to define some functions that are used in the macro twice, once in the .clj file and once in the .cljs file?
If you have all three files, I suspect that the *.cljc
file would never be loaded, according to the rules.
If the macro expands to make use of helper functions, then perhaps those could be placed in a *.cljc
file associated with another (impl) namepsace.
I guess making the convention for REPL usage to do the require first is OK. It would only have impact on REPL sessions, not project code, I guess?
There is no difference between the REPL and project code in terms of the need for client runtime namespaces to require clojure.spec.test.alpha
by that name, for the approach example I provided.
@mfikes would there be any downside of imposing this require clojure.spec.test.alpha
on consumers?
@borkdude And, truth be told, it requires your library clients to be on ClojureScript 1.9.198 or later
@mfikes what happens in JVM cljs if I put a defmacro in a .cljc not within a #?(:clj …)
? It works and I see no difference in the tests. Putting it within the conditional doesn’t work on planck.
I'm just saying, when a (defmacro ... )
form is compiled, if the ClojureScript compiler is the thing doing the compilation, you get a function instead. This is because defmacro
is itself a macro that expands to involve defn
.
In self-hosted ClojureScript, that function is called at compile time if it is being used as a macro.
Outside of self-hosted ClojureScript, you generally would only have defmacro
forms being compiled by the Clojure compiler.
Forms in .clj
files will end up being compiled by either the Clojure compiler, or the ClojureScript compiler (in the case they are being required as macro namespaces by self-hosted ClojureScript).
Forms in .cljc
files follow the same rules essentially, with the added rule that you can force some forms to be in either :clj
or :cljc
branches of reader conditionals if you wish.
Going back to your original question, the answer is that Planck, being self-hosted, is compiling the *cljc
file with the ClojureScript compler, and thus it takes :cljs
branches.
Looking at Clojure, the bit about defmacro
producing a function appears to be true in Clojure as well.
I think it’s still unclear to me what the behavior exactly is when I require a .cljc file with a macro in the top level (no reader conditionals) from a CLJS file
hey… i’m playing around with clojurescript, and wanting to create a multi-method that dispatches on every clojurescript datastructure and primitive type. Obviously I can use a dispatch function of type
but I’d rather not dispatch on concrete types but rather the abstract types (identified by protocols), e.g. IMap, IVector etc… Is there a way to ask a value for all of the protocols that are extended to it; or some other way to do this?
is that (defmacro foo
now a defn
? will it show up in my bundle? what exactly happens if I try and call it?
@rickmoynihan I think a multi-method is the wrong tool. you’d probably want to use protocols and extend-type
I could certainly do that too… but I don’t want to extend every concrete type.
Well I specifically want to extend to all maps i.e. those that satisfy the marker protocol IMap; not every instance of map; of which there are many… e.g. PersistentArrayMap, PersistentHashMap etc…
same for vectors sets etc
I’m not sure there is a good way to do this though — in which case I’d have to make do with extend-protocol
etc
That’s what I was looking at… using satisfies?
… but there’s no point as I’d have to use that to build dispatch keys which I’d define… so it wouldn’t be extensible… might as well be a case statement
In clojure I’d use the underlying java interfaces - but cljs won’t have those.
I think the right thing to do is a case (`cond`) dispatch on the appropriate predicate functions e.g. map?
vector?
set?
etc…
yeah, I can’t find a way to dynamically determine what protocols a value may satisfy
I’m pretty sure there isn’t a way to do this
if it really needs to be extensible, then extend-protocol
is the way to go despite being a lot of typing 😅
I’ve settled on the cond
being the dispatch function; returning a dispatch key of e.g. ::map
. Mainly so each case can share the same name, and be isolated from the others.
Thanks for the sanity check though. I’ve done clojure for quite a while; but not so much cljs.
was hoping that protocols at the bottom etc would’ve helped here — but it seems not.
@lilactown @mfikes
> Looking at Clojure, the bit about defmacro
producing a function appears to be true in Clojure as well.
that bit wasn’t 100% clear to me either. In JVM Clojure (not CLJS) a top-level defmacro in .cljc will become a function?
@borkdude If you require the *cljc
file as a runtime namespace (not a macro namespace), yes.
cljs.user=> (set! *print-fn-bodies* true)
true
cljs.user=> (defmacro foo [bar])
#'cljs.user/foo
cljs.user=> foo
#object[cljs$user$foo "function cljs$user$foo(_AMPERSAND_form,_AMPERSAND_env,bar){
return null;
}"]
If you require the *.cljc
file as a macro namespace, then it will be compiled by Clojure, and Clojure does what it does with macros (compiles them to a function the compiler can call)
Perhaps there are so many variables at play that, really to avoid confusion, you almost need to provide a minimal bit of code along with a question 🙂
@mfikes ah, you were talking about cljs in that line. so when not using cljs, it just behaves as a normal .clj file… just checkin’ 😉
On one hand, you have the semantics of macros, which involves how they are compiled, and how they are used.
And on the other hand, you have *.clj
, *.cljc
, *.cljs
semantics and how they are required
Add to that the fact that ClojureScript supports macro-functions, and in that case, the one you get depends on whether it is in operator position or being passed as a higher order function.
It feels a bit like the JCiP book, where the truth is baroqueishly complicated, but, if you stick to a subset of patterns, you can keep it straight. But, like anything, when it breaks, it is good to have an understanding of the truth.
neh, I think I’ll read it regardless. but having a guide on macros in cljc would be superb
To be honest, I ended up writing http://blog.fikesfarm.com/posts/2018-08-12-two-file-clojurescript-namespace-pattern.html as a “JCiP-style-simplification” guide to the way I like to write macros for ClojureScript projects. (While ignoring the possibility of use from Clojure.)
yeah, that’s the thing. what if you also wanted to use this from clojure, where would the defn
go?
I don’t think enough people are writing portable libraries to have discovered any best practices—it always seems to result in some compromise.
if that were all, then I would do that. but what if lib.core
also exposes normal defns
for public usage.
Take a look at core.async
(which doesn’t address self-hosted), specter
, test.check
, quil
—they all seem to adopt more or less of a mess to pull it off. None are clean in the end.
test.chuck
also
It seems that if you want to target all three platforms, you need to be prepared for some funky code, no matter how you slice it.