This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2021-09-27
Channels
- # announcements (2)
- # asami (25)
- # babashka (124)
- # beginners (46)
- # calva (55)
- # cljdoc (70)
- # clojure (68)
- # clojure-australia (2)
- # clojure-dev (63)
- # clojure-europe (38)
- # clojure-nl (1)
- # clojure-spec (1)
- # clojure-uk (8)
- # clojurescript (56)
- # community-development (4)
- # conjure (1)
- # copenhagen-clojurians (1)
- # core-async (1)
- # cursive (3)
- # datahike (5)
- # datomic (183)
- # depstar (2)
- # figwheel-main (10)
- # fulcro (20)
- # honeysql (2)
- # hyperfiddle (1)
- # integrant (68)
- # jobs (6)
- # jobs-discuss (5)
- # juxt (1)
- # malli (13)
- # off-topic (8)
- # pathom (2)
- # rdf (10)
- # reagent (11)
- # remote-jobs (1)
- # rum (1)
- # shadow-cljs (69)
- # spacemacs (1)
- # sql (5)
- # tools-build (51)
- # tools-deps (6)
- # xtdb (24)
Can anyone explain the rationale behind (get 1 :a)
returning nil
? I was under the assumption that (get m k)
was equivalent to (val (find (m k))
, but i just discovered that this is not the case when m
is not associative or a map
(get 1 :a)
equivalent to (get 1 :a nil)
meaning “return default value if the second argument is not a key in associative collection or first argument is not an associative collection”
https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/RT.java#L764-L787 for the reference
i've checked the definition in RT.java, i was wondering why it does not throw on non-associative args, like find
and contains?
do
this surprised me, get not throwing on non-associative args seems very error prone to me, so i was wondering if this is a bug, or if there is a good reason to return nil
okay, after checking what happens when it would throw, I see it breaks some things, for example in in clojure.spec.alpha there is some breakage here:
(defn- accept? [{:keys [::op]}]
(= ::accept op))
as this fn gets called with non associative args, and the destructuring expands to (get map_9996 ::op)
probably ship has sailed, but it'd be interesting to know the reasoning. it is documented though in the docstring of get
that it behaves this way (one could argue)
(doc get): Returns the value mapped to key, not-found or nil if key not present.
It is not very explicit though
get doesn't work only for associative types though. it works for strings for example. And it can be extended with ILookup
if you have something that returns a map or a scalar, you can preemptively destructure it or get
from it, even when it returns a scalar
(let [transact! (fn [xyz]
{:db-after :map}
::anom/conflict)
{:keys [db-after]} (transact! xyz)]
(if db-after
...))
is it by design or is this accidental? In contrast, sequence destructuring throws when destructuring a non-sequential value, for example (let [ [a b c & more] :scalar ] a)
throws java.lang.UnsupportedOperationException: nth not supported on this type: Keyword
hmm, i'm digging trough clojure.spec.alpha, where map destructuring is used in the way ghadi mentions several times, so i guess it is intentional
still, it seems very error prone to me, but maybe i've spent too much time in static typing land lately
cool, i didn't know :keys destructuring will also destructure seqs: (let [{:keys [a b c]} '(:a 1 :b 2 :c 3)] [a b c]) => [1 2 3]
@chris.blom it does. also in 1.11:
(let [[& {:keys [a b c]}] [{:a 1 :b 2 :c 3}]] [a b c])
[1 2 3]
the use case for that is primarily allowing maps to be passed to key-named argument functions
It has to be somewhere in spec, because they do something like (s/keys :req-un [::a ::b ::c] :opt-un [::d ::e])
Hmm it seems like the feature has been there since forever https://github.com/clojure/spec.alpha/blob/master/src/main/clojure/clojure/spec/alpha.clj#L444
@U064X3EF3 I recall you already explained me this and what changes for 1.11, would you mind saying that again quickly?
(! 837)-> clj -A:1.10
Clojure 1.10.3
user=> (defn f [& {:keys [a]}] a)
#'user/f
user=> (f {:a 1})
Execution error (IllegalArgumentException) at user/f (REPL:1).
No value supplied for key: {:a 1}
user=> (f :a 2)
2
user=> ^D
Tue Sep 28 15:29:56
(sean)-(jobs:0)-(~/clojure/fresh)
(! 838)-> clj -A:1.11
Clojure 1.11.0-alpha2
user=> (defn f [& {:keys [a]}] a)
#'user/f
user=> (f {:a 1})
1
user=> (f :a 2)
2
user=>
☝️:skin-tone-2: That is the specific change in Clojure 1.11.This is what folks used to have to do to "compose" such calls prior to 1.11:
user=> (apply f (mapcat identity {:a 1}))
1
This is the blog post from March that talked about it https://clojure.org/news/2021/03/18/apis-serving-people-and-programs
Oh ok so the specific change is the idea that you can use the same syntax with both a map and a vector with values inside @U04V70XH6
No...
No? I can see that the same function is not able to work with both a map and the arguments passed as key value params
These have always been different things:
(defn f1 [{:keys [a]}] ..) ;; accepts a single hash map as an argument
(defn f2 [& {:keys [a]}] ..) ;; accepts key/value named argument pairs
With 1.11, you can now use the second form everywhere and call it with either named argument (key/value) pairs -- as before -- or with a hash map (new)
In particular, this hybrid functionality is completely new:
user=> (defn f [& {:keys [a b c]}] (println a b c))
#'user/f
user=> (f :a 1 :b 2 :c 3)
1 2 3
nil
user=> (f :b 2 {:a 1 :c 3})
1 2 3
nil
@U04V70XH6 Do you happen to know what’s the rationale of using :a 1 :b 2
instead of a regular map?
This makes such functions much more composable because you can add new positional arguments in a given call as well as passing through an existing hash map of arguments.
@U015VUATAVC A lot of people think it's more "human-friendly". Hence the blog post I linked to above.
(foo :this "value" :and "some" :other "value")
is considered a "human interface"
(foo {:this "value" :and "some" :other "value"})
is considered a more programmatic interface -- and such calls compose much more easily.
I was excited enough about this new feature to immediately upgrade to 1.11 at work and we have code relying on this in production already.
(we have code relying on update-keys
and update-vals
in Alpha 2 in QA right now as well)
Yeah I recall you jumping on the ship early. You’re the official alpha tester here apparently
Last thing I need to figure out is my IDE. Right now I am on VSCode and Calva but I think it’s not the best. I have tried Sublime and Spacemacs but still, I can’t find something that fits 😞
I've gone through a lot of editors and IDEs and ended up at VS Code + Calva + Clover (because I don't use nREPL). What are you finding doesn't work for you about VS Code / Calva?
Is this the extension you’re talking about? https://marketplace.visualstudio.com/items?itemName=mauricioszabo.clover
I have always started my REPL outside my editor and just connected the editor to it -- my REPLs far outlive my editor sessions and often run for weeks, whereas I have to restart my editor for updates far more frequently.
And, yes, that is what I use -- with a Socket REPL which you have to start yourself outside VS Code -- and you have to disable Calva's nREPL UI (via settings) so that it doesn't get in the way.
We have Socket REPLs running in our QA and production processes too -- so I can use VS Code to work on "live" processes if I want.
@U04V70XH6 Why not nREPL, out of curiosity
a) I want the same editor/REPL experience regardless of which project I'm working on and whether the process is local or remote b) I don't want unnecessary dependencies in my deployed processes c) I want to be able to start up any Clojure process (or a non-Clojure process that uses Clojure as a library -- as is the case for us at work) and tell it to start a REPL on a given port, with no code needed and no dependencies needed. The Socket REPL satisfies all three of those requirements at the same time.
Clojure 1.11.0-alpha1
user=> (defn foo [& {:keys [a b]}] [a b])
#'user/foo
user=> (foo :a 1 :b 2)
[1 2]
user=> (foo {:a 1 :b 2})
[1 2]
yeah was a nice addition, really makes using [ & {:keys [a b c]} ]
much more useful