Fork me on GitHub
#cljs-dev
<
2019-02-26
>
gfredericks14:02:46

is it true that (:require [foo.bar :include-macros true]) causes the :include-macros true clause to effectively be implicitly present for requires of foo.bar in every other namespace?

thheller14:02:37

it is not something you should rely on

gfredericks15:02:49

I was hoping to rely on the opposite

borkdude15:02:31

you can maybe blow the analyzer state but I wouldn’t recommend it 😉

gfredericks15:02:37

Is the whole implicit sugar thing just a variant of this?

thheller15:02:12

it doesn't matter how the macro namespace is loaded. once it is loaded it will always be used.

borkdude15:02:34

maybe the question is: you should not rely on CLJS-2454, but isn’t the implicit sugar thing exactly this?

thheller15:02:52

not really. the :require-macros sugar ensures that the macros will be loaded and accessed properly

thheller15:02:19

without you are relying one something doing :include-macros true somewhere at least once in your build

borkdude15:02:16

isn’t include-macros true just sugar for require-macros?

thheller15:02:29

yes, but in a different place. it is added to the consumer ns, not the provider ns .. for the lack of better words ...

borkdude15:02:09

I see, so there is a different mechanism in self-requiring in the provider ns, or requiring the macros from a consumer ns?

thheller15:02:21

(ns foo.consumer
  (:require [foo.thing :include-macros true]))

(ns foo.consumer
  (:require-macros [foo.thing])
  (:require [foo.thing]))

thheller15:02:54

with the "self-require" the consumer does not need any knowledge of whether there are macros or not

thheller15:02:12

so it is shifted to the provider of said macros

thheller15:02:39

(ns foo.consumer
  (:require [foo.thing]))

(ns foo.thing
  (:require-macros [foo.thing]))

borkdude15:02:45

Understood yeah. But if a consumer ns1 would have used require-macros [foo.thing], could consumer ns2 get away with not doing that and still use the macros from foo.thing without a self-require? It seems to be that it’s the same mechanism (getting the analyzer in the right state), so I don’t get why you should not rely on it in CLJS-2454.

thheller15:02:50

as I said above

thheller15:02:58

> it doesn't matter how the macro namespace is loaded. once it is loaded it will always be used.

borkdude15:02:24

yes, but “you should not rely on it”, that’s the part what I’m asking about effectively 🙂

thheller15:02:36

you should not rely on it unless you can guarantee that the ns with the "side-effect" of loading the macros will ALWAYS be loaded first

borkdude15:02:22

right, it’s brittle because of that.

borkdude15:02:31

tl;dr: requiring macros in CLJS is side-effecting (once a var is identified as a macro, it stays a macro), so you should not rely on the order of loading namespaces. better is to do a self-require, then the side effect happens predictably and at the right time. is that a good summary?

Yehonathan Sharvit19:02:46

A question related to key and val. In Cljs 1.9, it used to work when we called key or val on a 2 element sequence. Now in Cljs 1.10 it breaks. I know that calling key or val on a seq is not accurate but it used to be tolerated. What’s the rationale of this breaking change?

Alex Miller (Clojure team)19:02:52

I don't know anything about cljs, but I'd say that is fixing something broken

borkdude19:02:06

alignment with Clojure probably

favila19:02:14

I know key and val would work on vectors, but really sequences?

borkdude19:02:23

did this maybe change when MapEntry was introduced as a proper type in CLJS? Before that 2-element vectors were used.

favila19:02:36

it's related to that change

favila19:02:53

but I am surprised that even then (key (list 'a)) would have worked

borkdude19:02:25

Most recently merge-with was also brought up to date using MapEntry which broke secretary, the client side routing lib

favila19:02:31

(key ['a']) definitely would have worked, and that stopped working because of MapEntry

favila19:02:56

rather, because a proper MapEntry type was introduced to align with clojure

borkdude19:02:37

yes, it’s also more performant (I’ve heard)

favila19:02:55

that wasn't really the motivation

borkdude19:02:06

I said also 🙂 (nice bonus)

favila20:02:45

this just added the type. the real work was done later

borkdude20:02:58

Here’s the patch for merge-with, showing some perf benefits: https://dev.clojure.org/jira/browse/CLJS-2943

favila20:02:12

i.e. converting PHM PAM etc to produce and consume them, and fixing key and val

Yehonathan Sharvit20:02:58

In the app of my customer, there are hundreds of occurences of key, val and vals

Yehonathan Sharvit20:02:20

I am very tempted to add an implementation of IMapEntry for all the sequences. Something like:

(extend-protocol IMapEntry
  PersistentVector
  (-key [v] (first v))
  (-val [v] (second v)))

Yehonathan Sharvit20:02:30

It feels like a dangerous hack

borkdude20:02:15

could work as an intermediate solution to an upgrade. emit a warning in those implementations and fix them one by one?

favila20:02:52

@viebel I am really suspicious about your claim that any seq could use key and val

borkdude20:02:13

I get a 404

favila20:02:34

I had some junk at the end sorry

favila20:02:56

CLJS-2478 is the ticket

favila20:02:09

as of that commit, PV no longer implemented IMapEntry

favila20:02:27

but before that commit only PV and MapEntry implemented IMapEntry

favila20:02:01

and the key and val functions have always only immediately called -key and -val

Alex Miller (Clojure team)20:02:51

the idea with key and val is that they are "slots" in a tuple, directly accessible via a lookup (not a traversal)

Alex Miller (Clojure team)20:02:57

MapEntry is a custom 2-tuple for it

Alex Miller (Clojure team)20:02:13

vectors are plausible (as slots with direct access)

Alex Miller (Clojure team)20:02:34

seqs are right out and you should feel bad if you did that :)

Yehonathan Sharvit20:02:44

My customer did that and he really feels bad

favila20:02:08

but he was only using it improperly on PV; otherwise his code would have exploded

favila20:02:32

so your extend-type is a scary but plausible workaround

favila20:02:44

you don't need (nor should you) extend to any other type

Alex Miller (Clojure team)20:02:37

I would say there are mixed feelings around "vectors of exactly length 2" being map entries. kind of a kludge but also awfully useful. Ideally we'd have actual fixed length tuples and use a 2-tuple. Maybe someday.

favila20:02:29

@alexmiller I think the issue here is he is upgrading clojurescript, and clojurescript used to impl key and value on PV, but now doesn't because it introduced MapEntry

favila20:02:39

so he has a codebase that used to work and now doesn't

Alex Miller (Clojure team)20:02:53

clojure has shifted a bit on this too

favila20:02:56

becuase he was accidentally using key and val on PV that were not of map-like provenance

favila20:02:21

It has? I don't remember key/val ever working on PV

favila20:02:32

(in clojure)

Alex Miller (Clojure team)20:02:16

we went down the tuple path for a bit in 1.7(?) and I think there were some alpha/betas where that worked

dnolen20:02:51

@viebel it was always an implementation detail

dnolen20:02:59

and it never worked in Clojure

dnolen20:02:38

if your application depends on that detail then your protocol trick seems fine to me

dnolen20:02:01

alignment with Clojure on MapEntry has a bunch of benefits

borkdude20:02:05

conj still works with a 2-vec on maps

borkdude20:02:23

(in both Clojure and ClojureScript) - I guess that’s also convenience, expecting people to manually create MapEntries is also not very nice. On the other hand, you can use assoc.

dnolen20:02:24

and the previous behavior wasn't something that people were consistently relying on - and the number of complaints hasn't been that significant

dnolen20:02:15

@borkdude that doesn't seem that related

borkdude20:02:33

just to illustrate that there are still places where 2-vecs are used semantically to represent a MapEntry. same for printing map-entries: they are printed as 2-vecs (which sometimes leads to confusion)

dnolen20:02:51

I would probably argue differently I think in these cases

dnolen20:02:36

MapEntry should probably be considered an implementation detail fundamentally

dnolen20:02:05

so there isn't a real discrepancy here

dnolen20:02:29

the only issue is people treating 2 element vectors unrelated to maps

dnolen20:02:33

as things that support key and val

dnolen20:02:43

the other points just don't seem relevant to me

dnolen20:02:51

(though of course yes easy to see that all those points muddy the waters)

borkdude20:02:16

do you consider key and val implementation-level functions?

dnolen21:02:54

no but that's not in conflict with what I said above

dnolen21:02:02

"something you got out of a map"

dnolen21:02:42

so the breaking change is that some random vector responds to key and val

dnolen21:02:34

but of course fixing that unbroke other very basic things