clojure

New to Clojure? Try the #beginners channel. Official docs: https://clojure.org/ Searchable message archives: https://clojurians-log.clojureverse.org/
yuhan 2021-04-13T00:46:39.421100Z

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

yuhan 2021-04-13T08:40:50.434900Z

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

nilern 2021-04-13T09:59:50.435100Z

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 šŸ˜œ

2021-04-13T13:16:07.458800Z

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

ā˜ļø 1
devn 2021-04-19T00:37:01.238600Z

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

2021-04-13T00:50:02.421800Z

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

2021-04-13T00:50:52.422Z

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

2021-04-13T00:51:19.422200Z

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?

yuhan 2021-04-13T00:53:28.422400Z

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

yuhan 2021-04-13T00:55:06.422600Z

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

yuhan 2021-04-13T00:56:30.422800Z

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

em 2021-04-13T00:57:45.423Z

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

em 2021-04-13T00:58:28.423200Z

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

2021-04-13T00:59:20.423400Z

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

2021-04-13T01:00:48.423600Z

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

šŸ’Æ 1
2021-04-13T01:01:16.423800Z

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

yuhan 2021-04-13T01:13:50.424100Z

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

yuhan 2021-04-13T01:15:01.424300Z

or actually just

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

ā˜ļø 1
2021-04-13T01:18:53.424600Z

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

2021-04-13T01:21:04.424800Z

So say you had this:

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

2021-04-13T01:21:23.425Z

I would make that more concise by doing this:

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

2021-04-13T01:22:18.425200Z

where init-start returns:

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

yuhan 2021-04-13T01:24:58.425400Z

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

yuhan 2021-04-13T01:26:49.425600Z

ie. why not

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

yuhan 2021-04-13T01:29:27.425800Z

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

2021-04-13T01:29:55.426Z

Yeah I donā€™t know what the START fn does/returns.

2021-04-13T01:30:41.426300Z

I was just riffing on your initial suggestion of:

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

šŸ‘Œ 1
2021-04-13T01:31:42.426600Z

But yeah, I also lean toward ā€œexpanding early.ā€ Removes a lot of rigamarole.

2021-04-13T01:32:02.426800Z

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

2021-04-13T01:32:22.427Z

ymmv ā€” thatā€™s just my tendency

yuhan 2021-04-13T01:50:15.427200Z

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

NoahTheDuke 2021-04-13T01:59:41.428500Z

Iā€™m reminded of the difference between Compojure and Reitit

2021-04-13T05:23:04.429700Z

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.

nilern 2021-04-13T10:03:49.435300Z

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

nilern 2021-04-13T10:10:56.435500Z

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 Stern 2021-04-14T19:23:09.030100Z

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)

2021-04-22T01:42:32.418700Z

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

2021-04-22T01:44:35.418900Z

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.

2021-04-22T01:45:32.419100Z

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

seancorfield 2021-04-22T01:59:54.419400Z

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.

2021-04-22T02:01:04.419600Z

*around my region that is

seancorfield 2021-04-22T02:02:25.419800Z

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.

šŸ‘ 1
2021-04-13T05:31:25.431Z

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... šŸ™‚

2021-04-13T05:34:48.431700Z

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

2021-04-13T14:47:27.459200Z

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

2021-04-13T14:47:49.459400Z

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

cassiel 2021-04-13T15:16:33.459600Z

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.

seancorfield 2021-04-13T16:46:20.495700Z

@cassiel My PhD was also in the ā€™80s ā€œThe Design and Implementation of Functional Programming Languagesā€ ā€” interesting times in the FP world back then.

cassiel 2021-04-13T16:47:07.496100Z

I think we had almost the same title.

seancorfield 2021-04-13T16:48:03.496500Z

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

cassiel 2021-04-13T16:50:06.497900Z

Well, as it happens he was my external examinerā€¦

seancorfield 2021-04-13T16:51:05.498100Z

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

cassiel 2021-04-13T16:51:47.498800Z

You mean weā€™ve been accidentally aliased?

šŸ˜† 1
2021-04-13T05:41:03.431800Z

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

seancorfield 2021-04-13T05:47:33.432Z

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.

seancorfield 2021-04-13T05:48:21.432200Z

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.

šŸ‘ 1
phronmophobic 2021-04-13T06:11:24.432600Z

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

vemv 2021-04-13T10:11:42.436700Z

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)

borkdude 2021-04-13T10:12:31.437200Z

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

borkdude 2021-04-13T10:12:54.437600Z

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

borkdude 2021-04-13T10:13:07.438100Z

(= x (.getParent y)) ?

vemv 2021-04-13T10:13:44.438900Z

> 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

nilern 2021-04-13T10:14:07.439700Z

I would start with .toPath

vemv 2021-04-13T10:14:07.439800Z

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

nilern 2021-04-13T10:15:28.440600Z

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

borkdude 2021-04-13T10:16:25.441100Z

@nilern what specifics of Path makes this easier?

nilern 2021-04-13T10:17:38.441700Z

Paths as strings are not so robust and portable

borkdude 2021-04-13T10:18:24.442Z

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

nilern 2021-04-13T10:18:50.442400Z

I am still looking

borkdude 2021-04-13T10:18:59.442600Z

$ 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

borkdude 2021-04-13T10:19:33.442900Z

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

šŸ‘ 1
p-himik 2021-04-13T10:26:04.445700Z

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

borkdude 2021-04-13T10:26:50.446400Z

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-himik 2021-04-13T10:29:40.447100Z

Apparently, Path has startsWith.

šŸ‘€ 1
nilern 2021-04-13T10:29:59.447400Z

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

šŸ˜… 1
borkdude 2021-04-13T10:31:02.448100Z

ah, path has startsWith? nice

borkdude 2021-04-13T10:32:00.448300Z

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

borkdude 2021-04-13T10:32:17.448500Z

oh!

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

nilern 2021-04-13T10:32:53.449200Z

Having suffered through that interop, fs looks really sweet

p-himik 2021-04-13T10:33:53.449700Z

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

borkdude 2021-04-13T10:33:58.449900Z

yeah :)

1
nilern 2021-04-13T10:35:09.450300Z

It's this one: https://github.com/babashka/fs

borkdude 2021-04-13T10:38:10.450800Z

Here are the docs: https://babashka.org/fs/babashka.fs.html

borkdude 2021-04-13T10:41:50.451400Z

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.

1
erwinrooijakkers 2021-04-13T11:04:46.453300Z

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-himik 2021-04-13T11:09:07.453400Z

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

erwinrooijakkers 2021-04-13T11:12:17.453600Z

ah exactly, thanks šŸ™‚

erwinrooijakkers 2021-04-13T11:12:35.453800Z

not natively but itā€™s possible to extend IAtom

erwinrooijakkers 2021-04-13T11:12:55.454Z

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

borkdude 2021-04-13T11:13:41.454200Z

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

šŸ‘ 1
šŸ†’ 1
erwinrooijakkers 2021-04-13T11:14:40.454600Z

nice!

erwinrooijakkers 2021-04-13T11:16:39.454800Z

genius

erwinrooijakkers 2021-04-13T11:16:45.455Z

might just use that instead of ttl cache

erwinrooijakkers 2021-04-13T11:17:03.455400Z

any copyright? šŸ™‚

borkdude 2021-04-13T11:17:29.455600Z

I hereby permit the right to copy

erwinrooijakkers 2021-04-13T11:17:46.455800Z

haha thanks

Margo 2021-04-13T12:20:24.457300Z

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?

dharrigan 2021-04-13T12:22:58.457800Z

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

dharrigan 2021-04-13T12:23:58.458100Z

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

Margo 2021-04-13T12:24:16.458400Z

I think Ill go there! Thanks a lot!

cjsauer 2021-04-13T15:55:09.463400Z

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?

dpsutton 2021-04-13T15:58:09.465600Z

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

ā˜ļø 3
vemv 2021-04-13T15:59:35.467200Z

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

āž• 1
vemv 2021-04-13T16:00:14.467900Z

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

ā¤ļø 1
nilern 2021-04-13T16:02:32.468900Z

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

vemv 2021-04-13T16:05:18.470200Z

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

cjsauer 2021-04-13T16:07:01.470400Z

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ā€.

dpsutton 2021-04-13T16:09:42.472100Z

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

cjsauer 2021-04-13T16:10:31.473100Z

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.

cjsauer 2021-04-13T16:12:52.473200Z

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.

2021-04-13T16:14:01.474300Z

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

2021-04-13T16:14:25.474700Z

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

dpsutton 2021-04-13T16:14:44.475200Z

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

cjsauer 2021-04-13T16:17:30.477500Z

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

vemv 2021-04-13T16:17:54.477900Z

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

2021-04-13T16:19:49.478100Z

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.

2021-04-13T16:20:09.478300Z

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

vemv 2021-04-13T16:21:58.479600Z

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

cjsauer 2021-04-13T16:28:46.483100Z

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.

2021-04-13T16:30:05.484400Z

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

āž• 1
borkdude 2021-04-13T16:30:25.485600Z

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

borkdude 2021-04-19T16:20:18.250300Z

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

borkdude 2021-04-19T16:20:36.250500Z

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

cjsauer 2021-04-19T16:21:09.251100Z

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

borkdude 2021-04-19T16:22:03.251300Z

@cjsauer do you use macos perhaps?

borkdude 2021-04-19T16:22:10.251500Z

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

borkdude 2021-04-19T16:23:49.251700Z

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

cjsauer 2021-04-19T16:37:51.252800Z

Yep Iā€™m on Mac. Where should I place the binary once compiled?

borkdude 2021-04-19T16:40:46.253100Z

Are you using Calva?

borkdude 2021-04-19T16:40:55.253300Z

Then it's under Calva settings

borkdude 2021-04-19T16:41:03.253500Z

the location is configurable there

borkdude 2021-04-19T16:42:21.254Z

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

cjsauer 2021-04-19T16:56:45.254400Z

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.

borkdude 2021-04-19T16:57:40.254600Z

Here's my local version: https://www.dropbox.com/s/j6prls3g2dl9u6s/clojure-lsp?dl=0

cjsauer 2021-04-19T17:10:16.255Z

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

šŸŽ‰ 1
2021-04-13T16:30:55.487200Z

circular deps are not an issue with keywords at all

dpsutton 2021-04-13T16:31:00.487500Z

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

ā˜ļø 1
cjsauer 2021-04-13T16:31:09.488Z

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.

vemv 2021-04-13T16:31:13.488300Z

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

2021-04-13T16:31:44.489200Z

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

2021-04-13T16:32:17.489800Z

no you don't

cjsauer 2021-04-13T16:32:18.490Z

Nice, thatā€™s pretty nifty

vemv 2021-04-13T16:32:20.490200Z

I tend to want this. :)

2021-04-13T16:32:33.490600Z

don't it is silly

ā˜ļø 1
borkdude 2021-04-13T16:32:44.491Z

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

šŸ’Æ 1
cjsauer 2021-04-13T16:32:56.491100Z

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

cjsauer 2021-04-13T16:33:06.491300Z

I canā€™t use :order/line-item-ids in that tx-map

vemv 2021-04-13T16:34:55.491600Z

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

lilactown 2021-04-13T16:35:00.491800Z

a transaction may be different than an entity

cjsauer 2021-04-13T16:35:31.492Z

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

lilactown 2021-04-13T16:36:15.492300Z

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

2021-04-13T16:38:18.492500Z

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

cjsauer 2021-04-13T16:38:27.492700Z

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.

cjsauer 2021-04-13T16:38:58.492900Z

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.

lilactown 2021-04-13T16:39:00.493100Z

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

lilactown 2021-04-13T16:39:47.493300Z

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

vemv 2021-04-13T16:40:22.493600Z

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

lilactown 2021-04-13T16:41:49.493800Z

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.

cjsauer 2021-04-13T16:42:31.494Z

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.

lilactown 2021-04-13T16:42:34.494200Z

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

2021-04-13T16:43:31.494400Z

it isn't

lilactown 2021-04-13T16:44:43.494700Z

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

2021-04-13T16:44:51.494900Z

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

āœ… 3
lilactown 2021-04-13T16:44:53.495100Z

which does make spec less attractice

2021-04-13T16:45:17.495300Z

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

cjsauer 2021-04-13T16:45:18.495500Z

Yea itā€™s a weird one. The schema is what allows for the disambiguation.

lilactown 2021-04-13T16:46:59.495900Z

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

lilactown 2021-04-13T16:47:48.496300Z

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

vemv 2021-04-13T16:48:14.496800Z

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

lilactown 2021-04-13T16:48:37.497Z

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

lilactown 2021-04-13T16:48:52.497200Z

so it's not useful in all contexts

2021-04-13T16:49:12.497400Z

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

2021-04-13T16:49:51.497700Z

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

cjsauer 2021-04-13T16:51:14.498300Z

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

vemv 2021-04-13T16:52:12.499100Z

> 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

2021-04-13T16:52:46.499300Z

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

lilactown 2021-04-13T16:54:26.499500Z

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

2021-04-13T16:54:27.499700Z

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

cjsauer 2021-04-13T16:54:38.499900Z

Yea Iā€™ve never actually written out #:foo{:bar ā€¦}, but I see it printed quite a bit.

vemv 2021-04-13T16:57:19Z

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

p-himik 2021-04-13T17:03:14.000300Z

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

cjsauer 2021-04-13T17:04:37.000500Z

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-himik 2021-04-13T17:05:42.000800Z

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

šŸ¤Æ 1
cjsauer 2021-04-13T17:06:06.001100Z

Oh wow. I didnā€™t know that was possible.

p-himik 2021-04-13T17:06:15.001300Z

:D Did I just convert you?

cjsauer 2021-04-13T17:06:38.001500Z

opens repl to testā€¦

cjsauer 2021-04-13T17:06:56.001700Z

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

cjsauer 2021-04-13T17:07:05.001900Z

I might have just been converted

šŸ˜„ 1
borkdude 2021-04-13T17:07:16.002100Z

you can also write

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

šŸ¤Æ 3
p-himik 2021-04-13T17:08:01.002500Z

Now it's my turn to use šŸ¤Æ

šŸ˜† 1
cjsauer 2021-04-13T17:09:14.002700Z

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

cjsauer 2021-04-13T17:11:34.003100Z

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.

cjsauer 2021-04-13T17:12:46.003300Z

With a web stack tho, thereā€™s just so many layersā€¦and several of them might benefit from specs ā€œa la carteā€

yuhan 2021-04-13T17:21:19.003600Z

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

šŸ˜‚ 3
šŸ‘Œ 1
yuhan 2021-04-13T17:22:18.003800Z

I wonder what a Obfuscated Clojure contest would look like

cjsauer 2021-04-13T17:30:08.004100Z

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

borkdude 2021-04-13T17:32:54.004400Z

@cjsauer filed: https://github.com/clj-kondo/clj-kondo/issues/1251

šŸ‘ 1
2021-04-13T18:03:34.005100Z

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

vemv 2021-04-13T18:26:32.005500Z

sounds correct! a classpath https://github.com/borkdude/grasp/tree/464f86af5866a134374af9ef7ed152882846b531#grasp-a-classpath can easily span multiple repos

šŸ‘ 1
donyorm 2021-04-13T21:15:24.007900Z

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

borkdude 2021-04-13T21:19:39.008200Z

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

šŸ’Æ 1
dpsutton 2021-04-13T21:20:53.008700Z

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