Fork me on GitHub
#clojure
<
2021-04-13
>
yuhan00:04:39

What are the design tradeoffs to building a custom keyword-based global registry like Spec or Re-frame handlers/subs, versus simply using Clojure's vars? (has anyone written articles or posts on this subject?)

potetm00:04:02

Well, for spec, the whole process is defining a spec for a keyword

potetm00:04:52

Can’t speak to reframe. I’m not sure what they do with them or why.

potetm00:04:19

Really it’s massively use-case dependent. I don’t think there are any general rules. Do you have a use case you’re curious about?

yuhan00:04:28

It's more a general question I was wondering about - I saw this "keyword registry" pattern in a few other libraries like Malli

yuhan00:04:06

Spec's different for sure, just referring to the idea of using a global registry to map keywords to some domain-specific data

yuhan00:04:30

I wonder if it's simply a matter of ergonomics (being more DSL-ey and not having to explicitly require and refer namespaces ), but you end up having to implicitly make sure that the ns registering those keywords has been loaded

em00:04:45

At a certain point once your DSL gets too complicated and bloated, you end up essentially reimplementing Clojure itself badly

em00:04:28

People are uncomfortable with serializing code as a rule sometimes, but DSL's with enough expressivity and the intent-to-run are code

potetm00:04:20

My tendency is to default to clojure vars until there is a concrete reason to move to keyword references.

potetm01:04:48

I think most people drift towards keywords first because they are more familiar.

💯 3
potetm01:04:16

But when you end up with a bunch of keywords that end up invoking functions elsewhere, you’ve kinda clearly gone too far.

yuhan01:04:50

That makes sense - so if I have an extensible domain-specific list of 'actions' that are meant to be interpreted in several places and invoke different functions, like

[{:type ::action/START
  :attr ::color/red}
 {:type ::action/PAUSE}
 ...]
It's probably better to model them first as vars like
(require '[foo.actions :refer [START PAUSE]])

[{:type START
  :attr ::color/red}
 {:type PAUSE}
 ...]
where each var is itself a data structure containing the necessary interpretations

yuhan01:04:01

or actually just

[(START {:attr ::color/red}})
 (PAUSE)
 ...]

☝️ 3
potetm01:04:53

Yeah in cases like these I always start with the data, then maybe move to a function for concision.

potetm01:04:04

So say you had this:

[(START {:attr ::color/red
         :state ::state/wait
         :name "hello!"})
 (PAUSE)]

potetm01:04:23

I would make that more concise by doing this:

[(START (init-start ::color/red
                    ::state/wait
                    "hello!"))
 (PAUSE)]

potetm01:04:18

where init-start returns:

{:attr ::color/red
 :state ::state/wait
 :name "hello!"}

yuhan01:04:58

hmm, so each action takes the same arglist format (for composability?) and you define preprocessor functions to pipe into them

yuhan01:04:49

ie. why not

(START ::color/red
       ::state/wait
       "hello!")
since the actions are also data->data transformers

yuhan01:04:27

I guess there's also the tension between "expanding" the DSL early vs keeping it user-readable through intermediate stages

;; Hiccup DSL
[:div {} args]

;; vs

(div {} args)

=> {:type ::dom-nodes/div
    :render-fn #function[0xabc3d]
    :html-name "div"
    :lots-of :noise
    ...}

potetm01:04:55

Yeah I don’t know what the START fn does/returns.

potetm01:04:41

I was just riffing on your initial suggestion of:

(START {:attr ::color/red})

👌 3
potetm01:04:42

But yeah, I also lean toward “expanding early.” Removes a lot of rigamarole.

potetm01:04:02

You can always remove things for readability. Can’t add things back in when you need them.

potetm01:04:22

ymmv — that’s just my tendency

yuhan01:04:15

Thanks a lot for your perspective - I'm not experienced in this sort of library design and wasn't able to find any best practice guidelines

Noah Bogart01:04:41

I’m reminded of the difference between Compojure and Reitit

phronmophobic06:04:24

One interesting design point is that afaik, spec only provides functions that work with the global registry. if you call (s/valid? ::my-spec m) and m includes an attribute, that attribute will be validated even if that attribute is never mentioned by ::my-spec. This is in contrast to the hierarchies for multi methods where both using a global registry or a local registry is supported. re-frame also has a global registry, but I think it's mostly a side-effect of the event system provided by the DOM (see https://github.com/day8/re-frame/issues/137). It's also probably partially influenced by clojurescript's reduced var support. If you're designing a system where the goal is for names and constraints to be enforced globally (like spec), then keywords support that more directly. By global, I don't mean just mean across a whole program, but across multiple programs or systems. For example, you could store a spec keyword in a file or send it across the network which is less direct if you were trying to use vars as names across programs and systems. This video touches on some of the reasons spec uses a global keyword registry rather than vars, https://vimeo.com/195711510

yuhan08:04:50

Thanks, that's quite enlightening! In our case the names will likely never leave the program, so that's another point in favour of using vars :)

nilern09:04:50

A mutable global registry is obviously bad because it is mutable and global. Malli also gives you other options. I don't want to debate the Spec design but I think the global registries are definitely unnecessary in re-frame and when I will end up making a frontend framework it will not have mutable globals 😜

potetm13:04:07

> A mutable global registry is obviously bad because it is mutable and global. This is one of the more Developer Brain sentences I’ve ever heard uttered.

☝️ 3
devn00:04:01

Nothin’ wrong with a global, mutable registry so long as it doesn’t change at any time you’d particularly care. Half-kidding.

kosengan05:04:04

random(not serious) question: what is the Haskell's equivalent of Lisp? (the answer should not include clojure) edit for "not sure if i get the question": the question comes from the context that, Haskell is not really a mainstream language, often considered academic, but somewhat foundational to learn and do some "real" programming - this can be said of Lisp as well. yet, Haskell has got a good community, and the language is comparably more active, we can see it being used in production to address specific use cases - this cannot be said about Lisp** You can hardly find someone or a company that is using Common Lisp ( I found Grammarly after some Google search) Scheme used at.. Naughty Dog? Clojure is good except it has a good audience complaining about being a hosted language. We can ignore them or try to convince them, but can't deny it.

hiredman05:04:03

This might be the series of blog posts for you https://crypto.stanford.edu/~blynn/compiler/

nilern10:04:49

That would be Racket https://racket-lang.org/ The one Naughty Dog used more recently

nilern10:04:56

There have been attempts to make a non-hosted Clojure or similar like https://github.com/pixie-lang/pixie but it seems that people did not actually want one as much as they wanted to complain about Java And now you can do scripting in cljs or babashka

Elliot Stern19:04:09

For some other uses of Common Lisp, Reddit used to be implemented in it, and Google uses it for airline search (via a company they acquired, ITA)

Daniel Tan01:04:32

I mean, Clojure and Scala are the only two mainstream functional languages that are being used at scale across different use cases That cannot be said of Haskell. Haskell is nice, but Haskell being more "active" is more of a surface phenomenon that arises out of its academic usage than its professional usage. You don't see people harp about c++ all the time and yet its still in the top popular languages

Daniel Tan01:04:35

Not sure why the question cannot include clojure because clojure is a lisp. If you're referring to CL implementations vs Haskell, CL is still being used in production, and the tooling is still under active development in Japan. It's just that Haskell has alot more english speakers.

Daniel Tan01:04:32

But Haskell in production? Heh I recently got my hands on a Haskell Library at work for evaluation but ultimately rejected it because you can't find anyone with related experience needed to juggle the clusterfuck that is Haskell in production

seancorfield01:04:54

I’m not exactly going to rush to defend Haskell’s ecosystem but I’ve talked to quite a few people over the years who are using Haskell in production — and often in seriously complex, critical usage situations — so I think that’s a bit disingenuous.

Daniel Tan02:04:04

*around my region that is

seancorfield02:04:25

I wish the Haskell folks would bite the bullet and treat the JVM as a first-class target so we could actually use it via interop and mix’n’match more. There’s Frege, which is actually really nice — I’ve done some Clojure/Frege interop — but it’s basically a one-man passion project so I would be concerned about its viability in production. There’s Eta, which came in with a fanfare (and, frankly, the project maintainers dumped all over Frege as not being “Haskell enough”) and has faded out as abandonware.

👍 3
Paul05:04:25

not sure if i get the question... will try to attempt anyway... so if clojure is a nice descendant of lisp... probably elm is a nice descendant of haskell... 🙂

Paul05:04:48

and if we're talking about origins... for haskell... it's (drum roll)

seancorfield05:04:33

Took me a while to get that… Miranda… but that’s a simplification: Haskell has its roots in a lot of languages that UK university researchers had been creating for many years before the committee got together to create Haskell.

seancorfield05:04:21

NPL, Hope, KRC, SASL, ML, SURE, … many, many FP languages were created in the ’70s and ’80s before Haskell appeared at the beginning of the ’90s.

👍 3
noisesmith14:04:27

right, Haskell was the common lisp project of the type safe / immutable world

noisesmith14:04:49

I tried to learn Clean at one point (much too early in my programming career to make sense of it)

cassiel15:04:33

I did my Ph.D. in the 80s and state-of-the-art then were Standard ML (eager) and KRC/Miranda (lazy, combinator-based as I recall). This was about the time that Glasgow were building combinator reduction hardware. For SML we were happy with our Sun SPARC workstations.

seancorfield16:04:20

@U6BUSDFMX My PhD was also in the ’80s “The Design and Implementation of Functional Programming Languages” — interesting times in the FP world back then.

cassiel16:04:07

I think we had almost the same title.

seancorfield16:04:03

(I never completed my write-up — Prof Turner from Kent was going to be my external examiner, according to my supervisor at Surrey)

cassiel16:04:06

Well, as it happens he was my external examiner…

seancorfield16:04:05

Hey! Maybe you’re me and neither of us ever knew it? :rolling_on_the_floor_laughing:

cassiel16:04:47

You mean we’ve been accidentally aliased?

😆 2
vemv10:04:42

is there a nifty way to check if a given http://java.io.file is a child of another? (may be N levels deep, so a naive check doesn't suffice)

borkdude10:04:31

@vemv (.getParent ...) returns nil if it doesn't have a parent I think?

borkdude10:04:54

oh you want to say (parent-of? x y) ?

borkdude10:04:07

(= x (.getParent y)) ?

vemv10:04:44

> oh you want to say (parent-of? x y) ? yes > (= x (.getParent y)) skips the N levels requirement ;p but thanks to duckie I think a solution is apparent now

nilern10:04:07

I would start with .toPath

vemv10:04:07

absolutize paths to strings, check with str/starts-with?

nilern10:04:28

I think java.nio.Path is the way to do just that properly

borkdude10:04:25

@nilern what specifics of Path makes this easier?

nilern10:04:38

Paths as strings are not so robust and portable

borkdude10:04:24

@nilern that doesn't answer my question though :)

nilern10:04:50

I am still looking

borkdude10:04:59

$ bb -e '(str/starts-with? (str (fs/real-path "README.md")) (str (fs/real-path ".")))'
true
that would work since real-path normalizes symlinks, relative paths, etc

borkdude10:04:33

@vemv fyi, this function is available in https://babashka.org/fs/babashka.fs.html which is based on java.nio

👍 3
p-himik10:04:04

fs/real-path returns a path without a trailing slash. Meaning, the check is not robust at all if "." is "/tmp/a" and "README.md" is actually "/tmp/aa/README.md".

borkdude10:04:50

true that. so you could use (seq (fs/real-path ...)) instead (this returns all the components) but is there similar starts-with? for seqs in clojure?

p-himik10:04:40

Apparently, Path has startsWith.

👀 3
nilern10:04:59

(-> (io/file "OpenSource")
    .toPath 
    (.toRealPath (make-array java.nio.file.LinkOption 0))
    (.startsWith (-> (io/file "/home")
                     .toPath
                     (.toRealPath (make-array java.nio.file.LinkOption 0)))))

😅 3
borkdude10:04:02

ah, path has startsWith? nice

borkdude10:04:00

$ bb -e '(.startsWith (fs/real-path "README.md") (fs/real-path "."))'
true

borkdude10:04:17

oh!

$ bb -e '(fs/starts-with? (fs/real-path "README.md") (fs/real-path "."))'
true

nilern10:04:53

Having suffered through that interop, fs looks really sweet

p-himik10:04:53

@borkdude Wait, you had fs/starts-with? all along? :D

borkdude10:04:50

Recently added this fun function:

$ bb -e '(map str (fs/modified-since "test" "src"))'
("src/babashka/impl/tasks.clj")
This can be used to create some Makefile-ish way of only updating when something has changed.

awesome 2
erwinrooijakkers11:04:46

is there a way to add a watcher to an atom that is fired not when the state change (like via add-watch), but instead when an atom is deref fed?

p-himik11:04:07

If you create a custom atom-like type, yes. Reagent does exactly that with its ratoms and reactions.

erwinrooijakkers11:04:17

ah exactly, thanks 🙂

erwinrooijakkers11:04:35

not natively but it’s possible to extend IAtom

erwinrooijakkers11:04:55

the behaviour i am looking for is basically same as a TTL cache so i use that

borkdude11:04:41

(def my-deref (let [a (atom 1)
                    deref-fn (fn [] (prn :foo))]
                (reify
                  clojure.lang.IDeref
                  (deref [this] (deref-fn) @a)
                  clojure.lang.IAtom
                  (swap [this f]
                    (swap! a f)))))

@my-deref ;; prints :foo
(swap! my-deref inc)

👍 3
🆒 3
erwinrooijakkers11:04:45

might just use that instead of ttl cache

erwinrooijakkers11:04:03

any copyright? 🙂

borkdude11:04:29

I hereby permit the right to copy

Margo12:04:24

Good afternoon everyone! I have recently purchased a new Mac M1 with Apple silicon and it looks like kaocha doent work in there. Can anyone help me or point to the right channel?

dharrigan12:04:58

You will have to elaborate further on the symptoms you are experiencing. There is the #kaocha channel too.

Margo12:04:16

I think Ill go there! Thanks a lot!

dharrigan12:04:58

I have a Mac Mini M1. I can run kaocha without any issues.

cjsauer15:04:09

Have others found the ::thing/id qualified keyword sugar to be harmful? Everything just works so much smoother for me when I type out qualified keywords in full…specifically surrounding: • Circular dependencies • Confusing lint tools into thinking namespaces are unused (mainly with cljc) • Fragility when moving functions/data to new namespaces (ie ::xyz can’t “travel” at will) • grep for aliased keywords may be unreliable • …others?

dpsutton15:04:09

i've settled on a pattern that i mostly never use auto-resolve keywords from other namespaces. i treat auto-resolving keywords to the current namespace as marking that keyword as "private" and then use more generic namespaced keywords (not tied in any way to a ns) when they need to be referenced from multiple namespaces

☝️ 6
cjsauer16:04:01

Nice. This makes sense. Then it’s a convenience for a keyword being internal to a namespace, with zero risk of colliding with any user keys that happen to flow through. Just have to be really careful that they never “escape”.

dpsutton16:04:42

it's not escape per se but no one else cares. and in that sense it's quite easy to do. Think of a middleware for a handler. if it wants to annotate the request with a start time and then an end time afterwards, it can add ::start-ms or something, and this is a sign that only this namespace is aware of, and cares about it. If it's introducing something that other things should care about, say user details it should use :cached/user or :context/user or whatever you think makes the most sense. Then it's clear this is introduced for others. In both cases the keyword "escapes" but the use is clear if its intended for others to access or not

cjsauer16:04:52

Interesting. How would this work with something like datomic schema, where my keywords are scoped to my domain? Like :org.company.model.user/id? It’s not really private to any one namespace, but still deserves the fully qualified name.

andy.fingerhut16:04:01

Isn't the approach there simply to always refer to those with the full name :org.company.model.user/id ?

andy.fingerhut16:04:25

You can do that, regardless of whether a particular project has a namespace org.company.model.user or not, I thought.

dpsutton16:04:44

in that case you've already gone down the road of tying keywords to namespaces. I would ignore the fact that there happens to be a ns that currently shares the same name and always use the :org.company.model.user/id

cjsauer16:04:30

> Isn’t the approach there simply to always refer to those with the full name That’s what I do yea. I was more curious whether it was an exception to the above guideline. Another example, pathom places a lot of fully domain qualified keywords into the parsing environment, with the intent that you can modify them and analyze them. So they’re not really “private” per se (or maybe they are and I shouldn’t be poking them lol).

vemv15:04:35

I agree on the practical flaws, but for me that indicates that the tools should be improved, instead of making my clojure idioms more rudimentary more often than not they can be improved with a very moderate effort being necessary

2
vemv16:04:14

the rebirth of rewrite-clj in particular will be a game-changer

❤️ 2
nilern16:04:32

grep will never be aware of namespaces but file bugs for the linters?

vemv16:04:18

I foresee https://github.com/borkdude/grasp or similar tools being the future of clojure grepping :) far more semantic, reducing false negatives/positives

apt18:04:34

interesting, thanks for mentioning this if I’m correctly understanding what this does, the binary could be used in multiple repositories for searching all occurrences of some symbol, which is quite helpful for finding deprecated functions usages

cjsauer16:04:31

I disagree a bit with wanting tooling to step up. More rudimentary clojure idioms actually seems kind of nice…it affords simplicity for the cost of a little convenience. I’m getting pretty used to the extra keystrokes and visual noise. The auto-completion helps quite a bit tho, which is admittedly a tool ha.

vemv16:04:54

yeah more than one way to skin a cat :) the circular dependencies point is interesting as it is tooling-independent. A pattern that has worked well for me over the years is a namespace dedicated solely to specs/keywords. I call it kws.clj (shorthand for "keywords"). Could be called specs.clj as well (although I don't like that choice for unrelated reasons) ...It's not merely a workaround, but a design choice that can yield a clearer API/impl separation

andy.fingerhut16:04:49

There is no need to create a namespace that matches the qualifier part of a qualified keyword, right? i.e. you can use the keyword :foo.bar/baz regardless of whether there is a namespace foo.bar or not.

andy.fingerhut16:04:09

as long as you are willing to copy/paste (or type, or use IDE auto-completion) its full name

vemv16:04:58

yes, but 90% of my ns-qualified kw usage is spec-related. The spec semantics imply an underlying implementation, which increases the chances for having an actual circular dep

cjsauer16:04:46

I’ve experimented with that approach as well. I still can’t quite get my head around how to use spec. What’s always irked me is that a keyword isn’t really always the same “shape” everywhere, as much as spec would like it to be (with its global registry). Here’s an example: say I spec a keyword called :order/line-items. This keyword can have any number of values depending on the context where it’s used. As a vector of entities: {:order/line-items [{:li/id "1"}, {:li/id "2"}]} but also as a vector of strings if we’re using datomic temp-ids: {:order/line-items ["li-1", "li-2"]}, or sometimes not even as a vector, and just a single temp-id {:order/line-items "li-1"}… So I never quite perfected the approach of putting them all in one place as a source of truth, because they’re just so contextual by nature. Could be why qualifying them is difficult.

hiredman16:04:05

should be :order/line-items and :order/line-item-ids

2
cjsauer16:04:09

That won’t work tho. Because then I can’t validate my transaction map that uses temp-ids. I have to use the keyword as it exists in my database schema.

hiredman16:04:17

no you don't

cjsauer16:04:56

(d/transact conn {:tx-data [{:order/line-items ["tempid-1" "tempid-2"]}]})

cjsauer16:04:06

I can’t use :order/line-item-ids in that tx-map

lilactown16:04:00

a transaction may be different than an entity

cjsauer16:04:31

Exactly, yet they use the same keyword. That’s my point. The same keyword can take different values depending on the context.

lilactown16:04:15

at the point you're transacting you're past the point of using spec, though

cjsauer16:04:27

I think that’s specific to that one example. Take the case of using idents. {:order/line-items [:li/id "123"]}. This could very well be pre-transact.

cjsauer16:04:58

You could use s/or in some way, but I don’t think that really covers it, because now it’s too admissive. There might be contexts in which idents really aren’t valid.

lilactown16:04:00

I believe the main point of spec is to have a stable definition within your program of what a key might be. e.g.

(defn order-total
  [{:order/keys [line-items]}]
  ,,,)
if order/line-items can be a map, or a coll of strings, or an ident then that's really not useful

lilactown16:04:47

the things you're talking about are necessary because at the edges of your program, it might not be in a useful shape yet to use throughout your program

lilactown16:04:49

between the database and the meat of your program there's typically code that will transform the shape of data into what it should look like.

cjsauer16:04:31

Yea. I suppose the lines of “your program” can get a bit blurry in web applications too, because you’re managing so many things at once. Imo, a global registry is just wrong. There’s simply no such thing as a “stable definition” at that level. Reductio ad absurdum: scope “your program” to be just a single function. That’s really the only level of granularity that allows a keyword to be stable.

lilactown16:04:34

e.g. you might transform {:order/line-items [:li/id "123"]} to {:order/line-items [{:li/id "123"}]}

lilactown16:04:43

I think if the novelty of your program is shuffling data to and from a database then the majority of your code will be handling your data in its un-conforming state

lilactown16:04:53

which does make spec less attractice

lilactown16:04:59

I've worked in webapps basically my whole career and that hasn't usually been the case; eventually there's a bunch of interesting business logic or some other process that needs to occur, e.g. taking data from one place, transforming it and annotating it and doing some logic, and then putting it somewhere else, which having a stable definition of what a key should look like or what an entity looks like is quite useful in that in-between step

lilactown16:04:48

and separating the I/O and the data shape required to effect changes with those two different systems is useful

lilactown16:04:37

and you're right, spec isn't going to ensure that my datomic transaction is correct, but it could check to see that my data is correct before I create the transaction

lilactown16:04:52

so it's not useful in all contexts

cjsauer16:04:14

That’s really the core of my struggle with it. It’s such a useful thing to have that specification to point at in those in-between steps like you mention. But I really only want to make that specification at that location. As implemented, spec forces you to make one grand statement about a keyword, and in my experience I always end up getting stuck. Basically, what if you didn’t use s/def at all, and only whipped up specs “on demand”? Like what https://github.com/metosin/malli does (specs as data).

lilactown16:04:26

I do think that having a global definition is a feature, not a bug. but I agree that it would be useful to opt in to - or override locally - that global definition

cjsauer17:04:34

It’s something that takes practice I think. I’ve attempted it several times and usually end up scratching my head. With a global registry, you have to be very intentional ahead of time what exact “level” you’re going to be running specs at. From your examples that would be at the validation layer of the stack.

cjsauer17:04:46

With a web stack tho, there’s just so many layers…and several of them might benefit from specs “a la carte”

borkdude16:04:25

@cjsauer re: https://clojurians.slack.com/archives/C03S1KBA2/p1618329309463400 use #lsp - find references will find all your related keywords, no matter their surface form. This is powered by clj-kondo analysis, which you can also use as a library if needed.

cjsauer16:04:18

Nice, that’s pretty nifty

cjsauer17:04:08

@borkdude playing with #:foo{:bar …} syntax in vs code. It seems to confuse #lsp when using “Find references” on the fully qualified keyword. The line numbers are way off. Where would I file this issue?

borkdude16:04:18

@cjsauer Fix is on clj-kondo master. I compiled a local lsp version with it and it now works.

borkdude16:04:36

So it's only a matter of time before it ends up in the downstream tools

cjsauer16:04:09

Very nice! Eager to try it out. Thanks for this.

borkdude16:04:03

@cjsauer do you use macos perhaps?

borkdude16:04:10

then I can just give you my locally compiled one ;)

borkdude16:04:49

You can also compile locally: https://github.com/borkdude/clojure-lsp and then run make prod-native

cjsauer16:04:51

Yep I’m on Mac. Where should I place the binary once compiled?

borkdude16:04:46

Are you using Calva?

borkdude16:04:55

Then it's under Calva settings

borkdude16:04:03

the location is configurable there

borkdude16:04:21

With emacs etc it's different. Best to ask in #lsp

cjsauer16:04:45

Calva yea. I found the setting. I might wait for the bot to catch up with releases. I think I botched something in my rushed attempt haha 😅 Looks like lsp will auto-bump kondo on the next cut.

cjsauer17:04:16

Thanks, got it working. Find references on keywords is working as expected now!

🎉 2
hiredman16:04:55

circular deps are not an issue with keywords at all

dpsutton16:04:00

i have a strong aversion to map entries having ambiguous cardinality. {:order/line-items "id"} vs {:order/line-items ["id"]}. that is doubly so when the string of the single item is seqable

☝️ 2
cjsauer16:04:18

Yea it’s a weird one. The schema is what allows for the disambiguation.

vemv16:04:13

specs are not contextual, that was talked about recently https://clojurians.slack.com/archives/C1B1BB2Q3/p1617647754017300

hiredman16:04:44

they are only an issue if you insist on closely coupling your code namespaces to keyword namespaces, and then tie your code namespaces into knots

vemv16:04:20

I tend to want this. :)

hiredman16:04:33

don't it is silly

☝️ 2
vemv16:04:55

it can be seen as an enforcement of decoupling, which is the opposite of having knots

hiredman16:04:18

you can see it that way, but the fact that it ties your code base in knots is evidence that seeing it that way is not correct

vemv16:04:22

I prefer to have (and solve) an explicit knot to an implicit one else the circular dep is there, it may just not manifest itself until much later

hiredman16:04:51

a keyword is a value, like the number 5, so when you using :foo/bar you don't implicitly have a dependency on the namespace foo, and you don't have a dependency on everywhere that keyword is used

7
hiredman16:04:17

just like using the value 5 doesn't mean you have some kind of dependency on wherever else 5 is used

vemv16:04:14

With spec in play though, consuming :foo/bar, where :foo/bar is backed by a spec, implies an implicit dependency on whatever ns is performing s/def :foo/bar

hiredman16:04:12

only if you are using spec features (s/valid? what have you)

hiredman16:04:51

and spec dependencies are much more lenient than clojure namespace/var dependencies

vemv16:04:12

> only if you are using spec features using a spec-backed defn is common enough yeah well I don't particularly appreciate that leniency for one thing it can result in spec checks not being performed because some ns didn't happen to be required by the consumer also can result in circular deps iirc

hiredman16:04:46

I dunno, I basically never spec fns, defn or otherwise

hiredman16:04:27

at work we have 34 s/fdefs and 1011 s/defs

vemv16:04:19

yeah varies widly per team. I tend to use s/fdef (or a variation) for most 'public api' defns. Accordingly I try to keep APIs small

borkdude16:04:44

I do agree that ::foo/bar and #:foo{:bar ...} etc have made it harder on clojure tooling. I don't even like the #:foo{:bar ...} notation myself, I prefer not to use it

💯 2
cjsauer16:04:38

Yea I’ve never actually written out #:foo{:bar …}, but I see it printed quite a bit.

p-himik17:04:14

It's useful when you have a large static map where many keywords have the same ns.

cjsauer17:04:37

But then you have the one odd-ball keyword that’s in a completely different namespace, and now you have to rewrite the whole map, or resort to merge haha.

p-himik17:04:42

user=> #:aaa {:b 1, :c/d 2}
{:aaa/b 1, :c/d 2}
And merge isn't that bad. :)

🤯 2
cjsauer17:04:06

Oh wow. I didn’t know that was possible.

p-himik17:04:15

:D Did I just convert you?

cjsauer17:04:38

opens repl to test…

cjsauer17:04:56

whelp, I had no idea you could opt out of the sugar like that!

cjsauer17:04:05

I might have just been converted

😄 2
borkdude17:04:16

you can also write

#:foo{:_/a 1}
{:a 1}

🤯 6
p-himik17:04:01

Now it's my turn to use 🤯

😆 2
cjsauer17:04:14

This actually makes typing out long namespaces (and avoiding ::sugar) that much easier.

yuhan17:04:19

#:_{:a 1 :_/b 2 #_:c}
;; => {:_/a 1, :b 2}

😂 6
👌 2
yuhan17:04:18

I wonder what a Obfuscated Clojure contest would look like

donyorm21:04:24

This may be a reach, but does anyone have a regex handy that matches the version numbers of clojure core? Trying to make a spec for matching Clojure versions

borkdude21:04:39

@donyorm Clojure has something better: *clojure-version* which is data

💯 2
dpsutton21:04:53

(source clojure-version) should give a structure of what to expect in the version string output from (clojure-version)