Fork me on GitHub
#clojure
<
2024-06-24
>
Hasan Ahmed02:06:50

https://clojureverse.org/t/should-we-really-use-clojures-syntax-for-namespaced-keys/1516/32 Hello, I came across above thread which I found really interesting especially because i am facing the same dilemma with passing data from/to js. I want to know more about the experiences of people who did clojure+js projects, how do you handle namespace(d) keys?

vemv02:06:24

One thing that has changed since that article is the rise of Malli, so a lot of people, by inertia are defaulting to non-namespaced keys. Most often Malli schemas are associated to a Var, so attribute namespacing is redundant. While Spec nudged one more heavily to use namespaced keys. (For clarity, neither tool really enforces a choice - just speaking about what feels 'natural' to most people) Nowadays, ideally I'll use non-namespaced keys for 'model' data (anything that might flow DB<->API), and namespaced keys for code (everything else) Although I'd happily change my mind if tooling support was different.

p-himik02:06:57

FWIW, I think that even with tooling support the model data wouldn't benefit from namespaced keys, at least in all the code that I've written. I remember trying this approach and dropping it because if previously I could create e.g. a label view that could be used for all items that have a label, now I have to also pass the label key all over the place, right from the moment I learn the type of a piece of data. The only instance where namespaced keys would help is when mixing data from different models. But I never do this, and don't remember seeing anyone do this except for maybe some SQL queries with a join. But at the join level, I also haven't seen any use cases where automatic namespaced keywords would be strictly better than clear explicit aliases.

Hasan Ahmed04:06:26

I can see that being practical. I guess what db is selected is a major factor (in my case, datomic). Models are expected to be namespaced in this particular case.

Hasan Ahmed04:06:45

an idea mentioned in the thread is to transform data on the edges by dropping or adding namespaces. Major gripe i have with this is data loss, this is especially painful for nested maps. thus, following the same principle, instead of dropping namespace, they'd be grouped. for example:

;; model 
{:user/firstname "Hasan"
 :user/lastname "Ahmed"}

;; at response becomes
{:user {:firstname "Hasan" :lastname "Ahmed}}
I hate the magic involved in this, but it's pretty trivial to implement (with some middlewares), then js/ts project is unaffected by clojure quirks and clojure things are handled as usual how bad of an idea is this?

seancorfield04:06:31

We used namespaced keys at work quite a lot -- for our domain model and also as an artifact of using next.jdbc with the default options. If you're generating JSON, the libraries all have an option to create unqualified "key" names for JSON so that's just not an issue.

seancorfield04:06:30

Incoming JSON is going to go through validation anyway so you'll validate it (as string-keys, ideally) and then build your (qualified key-based) domain model for internal use, and then as it passes out the other end of your code, either qualified keys matter (e.g., Datomic etc) or the persistence/JSON output will accept qualified keys and produce unqualified data (e.g., next.jdbc.sql accepts qualified keys and strips the qualifier).

seancorfield04:06:13

I will also say that, for the most part, the JSON APIs provided by our code are natural for JS and don't match what is natural for our domain model, nor entirely match our persistence model -- so there's already a mapping at the edges. That's why I'm a big advocate of qualified keys in the domain model -- because you are likely to have a transformation from/to JS data anyway (or from/to SQL or whatever). And if it so happens that your DB can handle your domain model as-is -- that's a win because you have less transformation to do.

7
seancorfield04:06:56

(FWIW, I strongly disagreed with most of Val's post and I think that "discussion" happened either on Reddit or directly on his blog and I didn't want to repeat it all on ClojureVerse -- and it's also a six year old post that I think hasn't aged well with the way things have gone since then in the Clojure world)

6
Hasan Ahmed05:06:39

This is the approach I currently following, receive unqualified then qualify then unqualify. It is an overhead that I'd like to get rid off. For every model I create, I have to have two interfaces/schemas and bi directional transformations

Hasan Ahmed05:06:16

There is the option of accepting and returning everything fully qualified and in js variables are accessed using 'user["user/firstname"]' instead of 'user.firstname'

Hasan Ahmed05:06:28

But this might cause things to break (i have not tested but something like v-bind on vue might have weird behaviours)

seancorfield05:06:22

It shouldn't just be qualify/unqualify tho', field names, overall data shapes, most of that should be transformed from what makes a good API to what makes a good domain model to what makes a good persistence model. Those three are (should be) all different so the transformation is already necessary -- and qualify/unqualify is the least of your concerns.

seancorfield05:06:21

(although with Datomic, you can probably omit the transformation between the domain model and the persistence model)

Hasan Ahmed05:06:51

You have any public project, you can point me to? I'd like to learn more about this

seancorfield05:06:15

Sorry, our apps are all private code (146k lines).

Hasan Ahmed05:06:10

That's alright. Thanks for everything

daveliepmann06:06:13

I prefer Approach 2 (systematic translation) and don't find the arguments against it persuasive. There should be a translation layer at the program boundary already/anyway. There's nothing "hacky" about it.

5
hifumi12319:06:57

I have used the munge/demunge approach with relative success. I was frustrated that up to four people in that discussion mentioned munge and didnt acknowledge the existence of demunge. In CLJS, demunge exists in cljs.core, but in CLJ it exists in clojure.lang.Compiler, also exposed publicly in clojure.main and clojure.repl.

hifumi12319:06:30

As far as I can tell, its totally safe to use demunge and its likely not in clojure.core because the core team sees no reason to add public functions to clojure.core when said public functions already exist elsewhere

gchape11:06:10

Has anyone had the same issue? I tried to disable signature-help for clojure-mode but couldn't get around it.

vemv11:06:43

Apparently it's (setq lsp-eldoc-render-all nil) (I searched eldoc in #lsp)

gchape12:06:14

(global-eldoc-mode -1) (after! lsp-ui (setq! lsp-ui-peek-enable t) (setq! lsp-ui-doc-show-with-mouse nil) (setq! lsp-ui-doc-show-with-cursor nil)) (after! lsp-mode (map! :map lsp-mode-map "M-?" #'lsp-ui-peek-find-references) (setq! lsp-ui-doc-enable nil) (setq! lsp-eldoc-render-all nil) (setq! lsp-eldoc-enable-hover nil) (setq! lsp-signature-auto-activate nil) (setq! lsp-signature-render-documentation nil)) (setq! cider-use-tooltips nil) (setq! cider-eldoc-display-for-symbol-at-point nil) (setq! cider-eldoc-display-context-dependent-info nil)

gchape12:06:54

I am cursed; I added it and it has no effect.😪

🌀 2
vemv12:06:37

What's the value of eldoc-documentation-functions at runtime?

vemv12:06:02

Weird. I'd ask over #CPABC1H61 since cider only 'speaks' eldoc

👍 2