Fork me on GitHub

Are ClojureScript macros typically defined using cljs.core/defmacro? Can they also be defined using clojure.core/defmacro ?


What I’m driving at here is to be able to distinguish macros targeting CLJS from ones targeting CLJ. Is it a fair assumption that macros defined using clojure.core/defmacro target CLJ and macros defined using cljs.core/defmacro target CLJS?


no, ClojureScript macros are typically defined in Clojure and use clojure.core/defmacro


e.g. you’ll have a .cljs file with your cljs code and a .clj file with macros that are included in your CLJS via (:require-macros) in the ns


the only time cljs.core/defmacro is really used is when using the self-hosted compiler. otherwise it’ll pop up in .cljc files but typically represents a no-op


So is there any useful way to determine whether a macro targets CLJS?


Thinking about it, sounds like no.

Roman Liutikov07:12:06

Do you mean if it's meant to be used in selfhosted cljs?


The issue is that in a CLJC file, when users try to get the doc in Cursive, it doesn’t show any. This is because Cursive resolves the symbol and it resolves to two places. In the case of first, it resolves to clojure.core/first and cljs.core/first . It’s not ideal behaviour, but Cursive doesn’t show the doc there because it doesn’t know which of the two to use.


My first attempt was to say that if one of the symbols resolved to something in a CLJ file, and the other resolved to something in a CLJS file, then show the doc from both. But that then still doesn’t work for macros - defrecord resolves to a definition in a CLJ file and another definition in a CLJC file, which defines a macro using both clojure.core/defrecord and cljs.core/defrecord.


It starts getting really complicated when you have something defined in a CLJC file which uses reader conditionals to define the same thing in two different ways. Then you get a different definitions in CLJ and CLJS, but they both come from the same CLJC file.


I was just trying to understand the semantics of CLJS macros better, which I probably investigated in depth a while ago, but have since forgotten since I don’t use them much (or at all).


Other tricky cases are things like +, which resolves to a function in CLJ but a macro in CLJS, unless it’s not in head position in which case both are functions. Cursive resolves that case accurately, but I have to take it into account for this.


As an aside, cljs.core is a real mind-bender.


My question above was really: can I tell if a macro is intended to expand to CLJS rather than CLJ? That would help me disambiguate these cases. But having thought about it, I don’t think it’s possible.

Roman Liutikov08:12:07

Yeah I think there's no way to differentiate here :(

Roman Liutikov08:12:44

Unless you could deduct this from usage of macro

Roman Liutikov08:12:56

If it's used in clj or in cljs files


Unfortunately that’s too slow to be useful.


@U0567Q30W macros can determine whether they should produce CLJ or CLJS code by checking &env. for you from the outside they are just regular CLJ macros with no difference really.


what might provide a hint is the namespace it is declared in and whether that namespace has a :require-macros in the ns.


so if you are in it has a (:require [some.thing :as x])


when checking x/thing you can check if either some.thing had a :require-macros for itself or whether did something with :require-macros or :include-macros


but in short you can't tell by the macro definition itself only by the context it is used in


@U05224H0W Thanks, I knew the background but the gotchas are very interesting.


Hi! I’m coming from a TypeScript/ES6 background. There is this way of assembling objects in JS without mutation using spread operators that is burned onto my prefrontal cortex and I lack the experience in Clojure to find alternative ways of doing this other than assoc constructs. Here’s what I would usually do in TS/ES6: const obj = { a: 1, b: { c: 2} }; const newObj = { ...obj, b: { c: 3 } }; // => { a: 1, b: {c: 3} } Here’s how I would do this in clojure: (def obj { :a 1 :b { :c 2 }) (def new-obj (assoc obj :b { :c 3 }) ; => { :a 1 :b { :c 3}} This is fine as long as I don’t have to deal with deeply nested maps but as soon as I’m trying to change a value nested three levels down, things start to get hairy and I’m constructing nested assoc calls. Is there some more elegant way of doing this with deeper nested maps?



👍 4
Roman Liutikov12:12:29

also take a look at update & update-in

👍 4

Wow, thanks!

Roman Liutikov12:12:13

@gerome.bochmann as JS dev this might be helpful for you Though could be outdated with respect to ES6, but still useful I think

👍 8
Filipe Silva18:12:03

testcafe seems to rely on some globally defined functions like test


Im gonna have a look at Takio 🙂. I have to read up on the differences though

Filipe Silva18:12:06

not too sure myself... I imagine those are being provided into window or something? maybe referencing js/window.test works


What is the generally recommended approach for writing ClojureScript code that calls into asynchronous JavaScript code? I was debating between the approaches of Promesa [1] and cljs-promises [2]. I’m essentially attempting to translate some conventional async/`await` JS code into CLJS and was hoping to find the most idiomatic way to tackle it. [1] [2]