Fork me on GitHub
#clojurescript
<
2018-11-10
>
anmonteiro16:11:19

@borkdude bear with me here

anmonteiro16:11:30

does it work if you add a (def foo 42) in repro.macros?

ClashTheBunny16:11:56

I think you need your macros to be in clj files

anmonteiro16:11:14

cljc files work just fine

borkdude16:11:38

I have a workaround for this, but I’d like to make sure if there is no better way

borkdude16:11:35

workaround looks something like:

(defmacro with-instrument-disabled [& body]
    `(choose-env :clj
                (clojure.spec.test.alpha/with-instrument-disabled [email protected])
                :cljs
                (cljs.spec.test.alpha/with-instrument-disabled [email protected])))

borkdude16:11:17

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)

borkdude16:11:35

I know this works, but I don’t know why this works 🙂

anmonteiro16:11:51

well there’s no runtime clojure.spec.test.alpha in CLJS

mfikes20:11:50

@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

borkdude20:11:11

@mfikes thanks. do you have an intuition what is the common way of handling this in other related libs?

borkdude20:11:52

maybe not a lot optimize for REPL usage?

mfikes20:11:17

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.

borkdude20:11:18

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?

borkdude20:11:22

or can you have a .clj, .cljc and .cljs file with the same name?

mfikes20:11:07

If you have all three files, I suspect that the *.cljc file would never be loaded, according to the rules.

mfikes20:11:38

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.

borkdude20:11:42

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?

borkdude20:11:41

but some functions are not impl, they are public API.

mfikes20:11:08

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.

borkdude20:11:10

but these can be re-def’ed from impl to public ns maybe

borkdude20:11:26

@mfikes would there be any downside of imposing this require clojure.spec.test.alpha on consumers?

mfikes20:11:53

It is really just an annoyance.

mfikes20:11:18

@borkdude And, truth be told, it requires your library clients to be on ClojureScript 1.9.198 or later

borkdude21:11:27

@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.

mfikes21:11:51

If a macro is compiled by ClojureScript, it produces a function

borkdude21:11:41

and if you put a :require-macros to the ns with a refer to the macro?

mfikes21:11:00

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.

mfikes21:11:10

Outside of self-hosted ClojureScript, you generally would only have defmacro forms being compiled by the Clojure compiler.

mfikes21:11:26

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.

mfikes21:11:57

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.

mfikes21:11:35

Looking at Clojure, the bit about defmacro producing a function appears to be true in Clojure as well.

lilactown21:11:50

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

rickmoynihan21:11:02

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?

lilactown21:11:41

is that (defmacro foo now a defn? will it show up in my bundle? what exactly happens if I try and call it?

lilactown21:11:49

@rickmoynihan I think a multi-method is the wrong tool. you’d probably want to use protocols and extend-type

rickmoynihan21:11:26

I could certainly do that too… but I don’t want to extend every concrete type.

lilactown21:11:42

sounds like you do tho? :P

rickmoynihan22:11:50

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…

rickmoynihan22:11:21

same for vectors sets etc

rickmoynihan22:11:48

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

lilactown22:11:02

well you could use extends? with a multimethod

lilactown22:11:12

hmm actually that might be hard

rickmoynihan22:11:58

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

rickmoynihan22:11:35

In clojure I’d use the underlying java interfaces - but cljs won’t have those.

rickmoynihan22:11:04

I think the right thing to do is a case (`cond`) dispatch on the appropriate predicate functions e.g. map? vector? set? etc…

lilactown22:11:39

yeah, I can’t find a way to dynamically determine what protocols a value may satisfy

lilactown22:11:50

reading the CLJS source is not very illuminating

rickmoynihan22:11:41

I’m pretty sure there isn’t a way to do this

lilactown22:11:52

if it really needs to be extensible, then extend-protocol is the way to go despite being a lot of typing 😅

lilactown22:11:24

if it doesn’t need to be extensible, then I agree with you: cond

rickmoynihan22:11:03

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.

rickmoynihan22:11:33

was hoping that protocols at the bottom etc would’ve helped here — but it seems not.

lilactown22:11:20

for sure! happy hacking

borkdude21:11:56

@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?

lilactown21:11:55

you can use extend-protocol to get rid of some of the boilerplate

mfikes22:11:08

@borkdude If you require the *cljc file as a runtime namespace (not a macro namespace), yes.

mfikes22:11:13

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;
}"]

mfikes22:11:29

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)

mfikes22:11:08

In that second case, the JVM ClojureScript compiler can also use that macro.

mfikes22:11:02

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 🙂

borkdude22:11:16

@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’ 😉

borkdude22:11:16

thanks for the elaborate response

mfikes22:11:33

Yeah, sorry, it is difficult to generalize without getting some aspect wrong.

mfikes22:11:13

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

mfikes22:11:42

I can imagine a 2 or 3 dimensional table descrbing what happens.

mfikes22:11:42

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.

borkdude22:11:35

such a dimensional table would be an awesome piece of documentation

mfikes22:11:17

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.

borkdude22:11:57

is this discussed in the clojure macro book maybe?

borkdude22:11:04

I don’t know if cljc already existed when that got written

mfikes22:11:38

Colin’s? He doesn’t even really discuss ClojureScript IIRC.

mfikes22:11:03

It is an awesome book regardless. I think I’ve read it 4 or 5 times :)

borkdude22:11:19

I think I’ll read it when he updates to cljc 😉

borkdude22:11:49

neh, I think I’ll read it regardless. but having a guide on macros in cljc would be superb

mfikes22:11:09

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.)

borkdude22:11:14

yeah, that’s the thing. what if you also wanted to use this from clojure, where would the defn go?

borkdude22:11:38

I guess you could then move that to the .cljc impl file

mfikes22:11:24

I don’t think enough people are writing portable libraries to have discovered any best practices—it always seems to result in some compromise.

borkdude22:11:38

if that were all, then I would do that. but what if lib.core also exposes normal defns for public usage.

mfikes22:11:27

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.

gfredericks22:11:39

test.chuck also

mfikes22:11:40

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.

borkdude22:11:08

ok. that’s what I wanted to know, now I don’t have to feel guilty 😉

mfikes22:11:03

The trick IMHO, is to keep the complexity and mess inside the lib. That way it works the same for all client code.

borkdude22:11:34

yeah, that’s what I was trying to accomplish.

borkdude22:11:48

so not imposing an extra require on the user was kind of my goal

mfikes22:11:44

Indeed—you can avoid that and push the complexity into the lib.