tools.deps question: I recently had an issue where enabling my :dev alias also enabled it recursively for one of my downstream dependencies, creating a version conflict between a Git SHA (top level) and a Maven coordinate (nested level) for the same library. Apart from adding an exclusion for every downstream dependency, is there a way to opt out of propagating an alias to downstream dependencies?
Aliases in downstream dependencies are ignored so it must have been some other issue. Also, there's #C6QH853H8
(this is actually a feature people ask for: the ability to "opt-in" to certain aliases in certain downstream deps)
well, I had a conflict, and the specific coordinate for the downstream dep was only declared in that dep's :dev alias, and adding an exclusion for that dep resolved the conflict. If I understand you correctly, I may have encountered a bug and may need to try to create a smaller reproduction of it.
Yeah, maybe. I'd love to see a repro, if you can make that.
Were you using tools.deps programmatically or just via the regular CLI?
you should be able to do something like:
clojure -Stree :aliases '[:dev]'
to see how deps were selected.probably
As in a vec of seq's might mapcat to an infinite seq?
Is there any big-picture documentation (or a blog post) on the checked-keys destructuring stuff in the 1.13 alphas yet? I keep running into cases where I want to check (and bind) the same key on multiple arguments. This works but it's fairly clunky:
(defn compare
[{a-val :foo/value :keys! [& :foo/value]}
{b-val :foo/value :keys! [& :foo/value]}]
...)
I'm wondering if I'm missing a more-blessed path here.Maybe the selector function introduced in alpha 8? Though I guess that doesn't make the destructuring transparent in the function arg list
Renaming is still pretty clunky right now, especially for qualified keys.
Yeah this is one of my concerns too. Especially in a datomic app where you have a bunch of different /id attributes and every keyword is namespaced, this is very clunky
i expect you're already used to writing a let, of course? you could do this yourself if you're wiling to give up the destructuring:
(defn compare [a b]
(let [a-val (req! a :foo/value)
b-val (req! b :foo/value)]
...))
Sure that works, but it's less desirable for all the same reasons why you might prefer destructuring to repeated calls to get: binding multiple keys, binding from nested maps, etc
oh yes, naturally, just wanted to make sure you were aware if you needed this specifically
I wonder if @richhickey has thoughts on this? It does seem to make qualified keywords harder to use around destructuring, except in simple cases.
I remembered Rich did mention this (https://clojurians.slack.com/archives/C06MAR553/p1783020088684719?thread_ts=1783015610.472469&cid=C06MAR553 and https://clojurians.slack.com/archives/C06E3HYPR/p1784067256313259?thread_ts=1784064711.243709&cid=C06E3HYPR) and @enn is using it correctly. Also https://clojurians.slack.com/archives/C06E3HYPR/p1785349500211399?thread_ts=1785285688.693499&cid=C06E3HYPR`:rename`https://clojurians.slack.com/archives/C06E3HYPR/p1785349500211399?thread_ts=1785285688.693499&cid=C06E3HYPR raised by @seancorfield. Seems to me this is mostly an edge case.
(And I'd argue having multiple /id attributes is not the correct – but definitely common! – modelling approach, but let's not get into that here 😄)
Is it tho'? @max.r.rothman makes it sound like this would be fairly common when dealing with Datomic.
Yeah it's extremely common with datomic or any software that deals with a graph of entities. You tend to want to destructure attributes from multiple connected entities, and those entities can and often do have attributes whose keywords differ only by namespace. For example, an invoice and its associated payment will both have an id, an amount, and a payee
This is off topic now, but:
I recognise it's common and practically idiomatic for Datomic. On the other hand, it seems to be in tension with Rich's ideas about RDF and reusing global attribute semantics. Are :invoice/payee and :payment/payee meant to be semantically different? If one is using say UUIDs everywhere, what's the point of having both :invoice/id and :payment/id?
Using namespaces as proxies for type/class/table is part of the same tension. It's natural to do it, it's a nice way of encoding the 'type' of a collection (map). But it's arguably overused.
(Relatedly, this is why I don't like lookup refs in Datomic, Datascript et al. The way they're commonly used smells of tables, complects type and identity and introduces annoying workarounds because :db/id is not meant to be public, etc. Of course, one could just have XTDB style global :xt/id on all entities in Datomic and my annoyance almost goes away.)
FWIW reusing attributes resolves some of the issues but not all of them. For example, a payment id might be an external identifier, eg stripe.ach-payment/id whereas an invoice might be an internal identifier, my.company/id. Those very well might be different types: the stripe id might be an opaque string whereas the invoice id could be a uuid, a number, etc.
The examples I'm giving here are simplistic for the purpose of demonstration, but suffice to say that this is a real problem that comes up frequently, to the point where the current codebase I'm working on hardly ever uses :keys when destructuring
I think the namespaced-keys/multiple-types aspect of this is a red herring. Think of all the operations you might do on several things of the same shape (with exactly the same keys, not differently-namespaced versions of them): merge or combine, compare according to some domain-specific ranking, diff, establish a parent/child relationship, etc. I don't think these are all edge cases.
If you're working with next.jdbc and doing JOIN operations, you'll get maps like {:user/id 123 :user/name "Sean Corfield" :user/age 64 :address/id 456 :address/state "OH" ..}
To destructure that, you'd have to do at least one renaming there. In Spec, :user/id and :address/id would be distinct attributes.
I guess {:user/keys! [id name age] :address/keys! [state & :address/id] address-id :address/id} isn't too awful but I don't know how to improve it without some sort of renaming annotation inside the :keys! directive here? {:address/keys! [{address-id :id} state] ..} perhaps?
(def data {:user/id 123 :user/name "Sean Corfield" :user/age 64
:address/id 456 :address/state "OH"})
(let [{:user/keys! ^u-[id name age] :address/keys! ^a-[state id]} data]
[u-id a-id])
=> [123 456]
(let [[{:keys ^x-[a b c]} {:keys ^y-[a b c]}]
[{:a 1 :b 2 :c 3} {:a 4 :b 5 :c 6}]]
[x-a x-b x-c y-a y-b y-c])
=> [1 2 3 4 5 6]
brainstorming, no promisessquints Hmm, looks like metadata on the vectors of binding symbols? Yeah, something like that would definitely ease this particular pain. 👀
The team has done some looking around and we’re not seeing this as a pervasive problem, annoying as it might be when it arises. If you have other evidence now would be the time…
I'm probably missing something here. But wouldn't it be simpler (and more generally useful in other scenarios where you might want to rename a binding) to add required-key support to the existing facility for destructuring a key to an arbitrarily-named symbol? Something like:
(let [{^:! u-id :user/id ...}
{^:! a-id :address/id ...}]
...)
Bonus: this would also make it possible to express multi-level destructurings with required keys at each level. (I haven't seen the talk yet--maybe multi-level destructuring is seen as something to discourage?)Nested destructuring with required keys already works in 1.13.
@seancorfield do you have an example handy? I can't picture it. I know you can do this: {{:keys! [foo bar]} :parent} to mark keys as required at the bottom-most level. But how can you mark :parent required?
> If you have other evidence
As much as I think some sort of renaming feature would be useful (and I've mentioned it before, and accepted that it would not be considered), I have only run into this in a couple of places and really only occasionally:
• When dealing with JOIN results from next.jdbc (see above in this thread, where I don't think the "workaround" is particularly onerous)
• When dealing with data from some payment systems where you can end up trying to work around two instances of data with either identical keys or an overlap of keys
@max.r.rothman mentioned Datomic but I can't speak to that.
@enn Here's an example from our work codebase:
(defn- service-start
[{:keys! [worldsingles-application members actioner database]
{:keys! [& :redis-pubsub] pubsub :redis-pubsub} :worldsingles-application
:as service}]That requires some top-level keys, requires a key (without binding) inside one of those top-level keys, and renames/binds that nested key.
Ah, I see, the same approach as my original example at the beginning of this thread. I guess I will have to live with the repetition.
My 2c is that it seems a little backwards from a design perspective to encourage the use of namespaced keys as a way to encourage flat maps without worrying about keyword name conflicts, then make tools that work best if all keyword names (the part after the "/") are unique. Certainly keyword name conflicts come up a lot in datomic, but the fact that a lot of software written in clojure doesn't use namespaced keywords is not necessarily a good reason to add friction to their use
“add friction” is a bizarre characterization of “fail to optimize for my anecdote” and exactly the kind of rhetoric that keeps me off social media
Not one person who asked for this bothered to write a single sentence of prose, instead throwing up presumed self evident exemplars accompanied by ‘clunky’. Any solution to this is going to add complexity for everyone
you are taking the feature that makes it easy to use the same name as the key for the binding and then simultaneously saying it should magically give you different names when you use the feature twice in the same binding context? Or what?
I did write some prose, but I also clarified that I don't run into this often enough for the existing approach to be onerous. Are there real world cases where it crops up? Yes. Are there common, frequent real world cases, where it's really a burden? Not for me, no. So, I'll butt out of this, and let others see if they can justify the feature request...
(and, just to be clear, I am already using the 1.13 features in production code—we have Alpha 9 on production, as of today, and we've been running all the previous alpha builds in production)
Sure, lemme take a step back and write up a little more detail...
@max.r.rothman no need, I fully understand. We aren’y seeing this enough in codebases to justify more features around it right now
What are you looking for then? Or have you made a decision and aren't looking for anything?
but it’s interesting to see how everyone’s perspective is shaped. E.g., there’s no such thing as renaming. There is only naming. In bindings, you need to choose (non-conflicting) names. That’s as old as Lisp and has nothing to do with maps. That we’ve added features to talk about the binding and a same-named key without redundancy was the feature add to the you-must-do job of picking names to use in local scope. Suddenly that becomes an expectation and anything you need to say twice is added friction.
That's fair. I guess to me it seems strange to complect throwing an error for missing keys with binding map values to names the same as (name <kw>), but I can only speak for my own experience and the codebases I've worked on so I have a narrower view than you.
As for friction, my experience is that namespaced keywords can get quite long, e.g. st.payment-order.line/payment-proposal-line, and when devs are pressed for time as they often are, they'll tend to skip things like typing a keyword out a second time to make it required when it would've been beneficial to do so. Also, large nested destructuring forms can already get hard to read, and having to type keywords out twice to both select their binding name and mark them as required exacerbates that.
I'm not a clojure language maintainer though, so I know I'm not entitled to anything beyond my own opinion, I just hoped this feedback might be helpful in the development of these new destructuring features.
I went through several dozen pages (100s or 1000s of examples) of search results in both github and in our private nubank github (which I think is probably a more likely "app"-y code base than open source libs). I was hard pressed to find any cases where this feature was needed. I did find some cases where a single let binding destructured two different maps with similar shape and thus needed to disambiguate "id" or whatever twice, but all of those cases were destructuring a single key from each map. The syntax Rich proposed above was longer than just doing the aliasing as currently done (but would have won if multiple keys were destructured). It's not hard for me to envision the original case, I just don't see any evidence that people are regularly encountering this now.
if you want to your own looking, https://github.com/search?q=path%3A*.clj+%2F%5C%7B%5Ba-z%5D%5Ba-zA-Z0-9-_%5D*+%3A%5Ba-zA-Z-_%5D%2B%5C%2F%2F&type=code (of several variants) of search I did - tweak the regex as you like (also looked at autoresolved keywords, etc)
I'm going to second some of @max.r.rothman’s concerns which is not surprising given that we work in the same codebase and I wrote much of it.
I'll start by saying I am looking forward to the new 1.13 syntax -we use deep and wide destructuring extensively in function args. I believe there is a valuable message sent when bindings are established early from input versus having the same binding spread out later in the body of the function through arbitrary code (which boils down mostly to getters). We also use preconditions on these deep/wide destructured locals. Being able to do that (and a lot more) in one step is a win regardless of the outcome of some nice-to-have additions to the syntax.
We use Datomic for our app. We use deep and wide Datomic pulls to bring our entity graphs back to our application and the consequence is that we frequently have the need to bind locals to entity attributes at many locations in the graph and sometimes those entity attributes collide on the name portion. When that happens, we create "custom" bindings instead of using :keys. FWIW, about 95% of the time the collisions come from distinct entity types in the graph that share a name but not a namespace -and this case matches @alexmiller's observation that only a single key collides (`:X/id` in our case as well). About 5% of the time however the collisions come from distinct entities of the same "type" in the graph that share a qualified name but reside at a different path in the graph (e.g. a LegalPerson with a unique :legal-person/id attr that can be both a CounterParty and/or a Signatory of a Contract entity). In this case every key of the LegalPerson-as-Signatory will collide with the exact same keys on the LegalPerson-as-CounterParty.
I anticipate limitations to the use of the new syntax with our extensive use of deep and wide destructuring combined with Datomic pull combined with heavy disambiguation from namespaces. But it will still be a step forward regardless and I appreciate the deliberate pace at which Clojure features are released -especially those that might close the door on other syntax stuff in the future.
I don't know if anyone has been messing with Zed + Clojure, but I decided to mess around with it today. Looks like we can get a repl file (no inline eval) and clojure-lsp motion pretty easily. Has anyone tried similar? If not, I'll post up my results some time tonight.
Last time I checked, their extension API didn't have a way to implement paredit (can't insert beyond the caret or something), so I abandoned the idea of having a great Clojure experience in Zed
I'm screwing around with a fork. I'll send deets later, but it's actually pretty small. I might be able to get it up stream. Basically code snippets, paredit, and regular eval all seem imminently doable. Tbh even if they don't like my change I can just get Claude to periodically rebase my change over master and not worry about it. I'm trying to leave VS code in the rear view mirror lol, it's such a hog on memory
There is no proper REPL integration so haven't looked at it for a while
It required a Jupyter kernel to interact with the REPL which was super inconvenient, other than that it is an excellent editor
I've used it with Elixir and it was nice too, but then I got the REPL working with Neovim on Elixir too and never looked back
@jatkin I would love to try this and also help with getting it to Zed (I wonder if this could be some sort of plugin) I really enjoy the simplicity and speed of Zed and would like it to have first-class Clojure REPL support
sg. Here's my updates: https://github.com/JJ-Atkinson/zed/tree/enable-clojure-workflows https://github.com/zed-extensions/clojure/compare/main...JJ-Atkinson:clojure:add-clojure-evaluation-workflow?expand=1 https://github.com/JJ-Atkinson/zed-clojure-eval https://gist.github.com/JJ-Atkinson/21173b470ad68ea967fb5c0d3fd37544 Sorry for the scattered locations of everything. Basically, the zed update allows LSP actions to be invoked directly via keybinds, the clojure extension edit modifies runnables, and the runnables include the bbin task zed-clojure-eval. My example config is included in the gist.