This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2021-10-01
Channels
- # announcements (5)
- # aws (2)
- # babashka (35)
- # beginners (68)
- # bristol-clojurians (1)
- # calva (14)
- # cider (9)
- # clj-commons (5)
- # clojars (3)
- # clojure (101)
- # clojure-australia (6)
- # clojure-europe (35)
- # clojure-italy (2)
- # clojure-nl (5)
- # clojure-spec (1)
- # clojure-uk (4)
- # clojurescript (70)
- # conjure (3)
- # cursive (7)
- # datomic (36)
- # emacs (11)
- # exercism (1)
- # fulcro (23)
- # helix (2)
- # jobs-discuss (15)
- # lsp (17)
- # malli (8)
- # meander (1)
- # nrepl (60)
- # off-topic (10)
- # pedestal (9)
- # react (8)
- # reagent (53)
- # reveal (10)
- # shadow-cljs (27)
- # spacemacs (4)
- # tools-build (4)
- # tools-deps (4)
- # xtdb (2)
(into
[]
(apply
concat
(for [{attachments :attachments} example-messages]
(seq (map :proxy-url attachments)))))
;; is it wierd to 'apply concat' to get rid of nils?
Whenever you apply concat or flatten to remove nils or empty sequences, regard it as a smell. You can always produce a sequence or transducer which won't have them to begin with
Or use the transducer arity of into
:
(def example-messages
[{:attachments [{:proxy-url "foo"}]}
{:attachments [{:proxy-url "bar"}]}
{:attachments [{:proxy-url nil}]}
{:attachments []}])
(into []
(comp
(mapcat :attachments)
(keep :proxy-url))
example-messages)
;;=> ["foo" "bar"]
in the original (seq (map ...))
is redundant, concat deals with nils just fine
To your question, I would say it isn't weird. Maybe a little confusing to the reader, but a comment about it or like others suggested a more obvious filter
or remove
would make it readable in my opinion.
I think what others are saying relate to performance, where if you can avoid the nils in the first place you'd get a small performance gain, but I would not be concerned with that unless it actually was an issue personally.
Though I would probably just write:
(->> example-messages
(mapcat :attachments)
(map :proxy-url))
Or if you really want it back as a vector:
(->> example-messages
(mapcat :attachments)
(map :proxy-url)
vec)
Or if you really cared about wanting it as a transducer:
(into []
(comp
(mapcat :attachments)
(map :proxy-url))
example-messages)
And use keep instead of map if you also want to exclude nil :proxy-url values, but your original code doesn't so I used map to keep the exact same behavior.
The Clojure is dynamic type. so I can't known what value inside the argument when I read the function. I need read code with context for understand. for example: In java class Student{ String name; Integer age; } foo(Studnet stu){ ...} I know what is stu. In clojure: (defn foo [ stu] ...) I don't noting inside stu. expect read the call foo function position. So. How do I understand this difference in Clojure
You can tackle this in many ways which include:
- consistent naming; See the core library or the book “Elements of Clojure” for good references.
- docstrings and other metadata; They are in-built in defn
s to provide information and documentation.
- contracts; You can put pre/post conditions into functions or write specs for data structures and functions alike. Clojure core has done a ton of work there to provide you with good error messages via specs, you can also use/build them to generate tests or to hook up instrumentation.
- type annotations; They are a type of metadata that you can attach to arguments. In specific cases they are used to avoid reflection by the compiler.
- third party libraries; there are libraries like schema, malli, typed and more that can help you solve this.
You can also use the REPL. For example:
;; Call clojure.pprint/pprint on every tapped value
(add-tap (bound-fn* clojure.pprint/pprint))
(defn foo
[student]
;; Tap the student argument
(tap> student)
;; Do whatever
,,,)
;; Call foo, or do whatever that results in a call to foo
(foo {:last-name "Smith"})
;; student gets printed into stdout
Printing the value is kinda the most lo-fi solution there is. You can also look into using tools like Portal or Reveal.I would also say, you need to think differently. In Java, you write methods that operate over the class fields. Which mean that you need to know what the fields are when writing the function and you expect the function to work over those fields. In Clojure you should write a function that operates over its own model. The function shouldn't have to care about some external representation. So for example, you might have:
(defn greet-student
[{:keys [name] :as student}]
(println "Hello " name))
Now you might ask, what is student
, and the answer is that student is any map with a key called :nameThere is no real concept of a student
as anything more then that. There are no defined student entity, just a function that greets the :name key on a map which we assume might represent a student conceptually.
That means now I can do all kind of things:
(greet-student {:name "Bob" :age 24}
(greet-student {:name "John" :email "})
(greet-student {:name "Emma"})
(greet-student {:name (:student-name student))
All these are valid, so my function is automatically quite generic, it works on many things as long as in the end I can adapt them to a map with a :name
key then I can use my function.Put in another way, you have to ask yourself, why do you care what is inside? And in Clojure, you should not have to care, if you have too, you might be designing things in an OO fashion and should try to re-think. Ideally in Clojure, it is the caller that needs to ask themselves: "What argument does this function want?", it should not be the callee that has to ask itself: "What is the structure of what I am given?" The latter is a code-smell in my opinion.
thank you @U0K064KQV
If I have a collection of strings (urls), and a function that queries that url that gets some details, is it normal to use map
here
(map my-http-query urls)
My thinking with map is that the fn
being mapped should be pure, or is this wrong? If its not wrong is their a more idiomatic side-effecting function that collects the results in the same vein as map
? If I use map could its laziness be an issue (maybe I wrap it in doall
? Or just use mapv
Yes, lazyness could be an issue, (you might not notice it with small sequences). I would wrap it in a doall
for sure. It also makes it more explicit that my-http-query
is side-effecting.
mapv (or transducers) are explicit about being eager, not so explicit about side-effects
@U01EFUL1A8M Could you elaborate on that? I've been using mapv when mapping through side-effects
@UB0S1GF47 which is fine, I’m nitpicking.
If you wrap something into a doall
then there the code basically screams “side-effect”. Using mapv
is just eager evaluation and specific to vectors. Personally doall
feels more right to me.
https://clojuredocs.org/clojure.core/doall
Ahh I see what you mean now. mapv
just so happens to 'run' the side-effect but the intent is implicit. I tend to wrap any side-effect code in functions with an !
at the end. But perhaps a doall
might also help to serve as a signal as you say
I don't think it matters much if you use mapv or doall, personally I find mapv more convenient since its shorter to type. I've never really felt like I needed doall to scream at me side-effect in order to not shoot myself in the foot.
What do you call "docker build" stuff? If you mean a few simple lines that call out to docker build, I'd reach for a shell script. No need to launch a JVM and it's made for handling CLIs. If you have a complex set-up making calls to the Docker API to build and/or run a lot of images in a very non-trivial way, I'd reach for Clojure (or any language where tooling is available team-wide).
Look into Babashka https://book.babashka.org/#tasks
Since I love Clojure, I obviously want to use it for every code I ever need to write, so instead of a shell script in Bash or Python, I personally use Babashka.
I also maybe disagree with @U02FU034U15, unless I need it to start really fast, I see no problem using Clojure JVM when you're already setup to be able to run Clojure. So I think also just using tools.build is fine if it lets you do what you want.
WARNING: update-keys already refers to: #'clojure.core/update-keys in namespace: clojure.tools.analyzer.utils, being replaced by: #'clojure.tools.analyzer.utils/update-keys
I know it's a bit ghetto to ask like this without a minimal reproduce but I have those warnings since I added org.clojure/clojure {:mvn/version "1.11.0-alpha2"}
dep. Is there some obvious reason for this?Ya, a lot of existing code used to define their own update-keys
, but now its a core function. You can mostly ignore the warning, and hopefully libraries will eventually update themselves as well.
I’ve also seen this @benjamin.schwerdtner
I guess that namespace was specifying that utility function on its own and it’s now conflicting with the builtin one in clojure.core
@benjamin.schwerdtner this is a periodic thing when clojure adds a new core function with an obvious or common name, for example update
caused the same warnings in many libs when it was introduced
I want to run through a vector of maps and if a map in question has a particular key-value I want to add 2 new key-vals to the map in the vector-of-mapz.
For example
[ {:id 1 :hax 5}
{:id 2 :hax 3}
{:id 7 :hax 9} ]
If there's a match on let's say :hax 9
I'd like to add 2 new keys to that map
[ {:id 1 :hax 5}
{:id 2 :hax 3}
{:id 7 :hax 9
:top "sweet"
:Bottom "nice"} ]
I was thinking this could be a job for reduce
but I'm not sure how I'd make it work.
map
instead of reduce
feels natural, as what you want is a simple element-wise operation; reduce
would come in handy if you were aggregating elements together
Ah, very nice
So then update-if-matching-kv
could take the map in and output a new map with assoc'd
new keys... if I understand you right
😄 🙏:skin-tone-4:
What if I wanted to pass in specific new keys and vals
?
(map update-if-matching-kv vector-of-mapz {new-keys, new-vals})
?
{new-keys, new-vals}
would probably have to be an arg in the update-if-matching-kv
now that I look at it
I guess map
with 2 input maps confuses me right now
Oh thanks, I figured it out using partial
(map (partial assoc-if-matching-kv {:le-key "le-val"}) vector-of-mapz)
and assoc-if-matching-kv
takes 2 maps 😎
https://clojurians.slack.com/archives/C053AK3F9/p1632266748285100?thread_ts=1632265705.280500&cid=C053AK3F9 ... There's also reductions ... Which might interest you?
o.O neat. I don't really get it though
Like this?
(for [m [{:id 1 :hax 5}
{:id 2 :hax 3}
{:id 7 :hax 9}]]
(if (= (:hax m) 9)
(assoc m :top "sweet" :bottom "nice")
m))
> What if I wanted to pass in specific new keys and vals ? > (map update-if-matching-kv vector-of-mapz {new-keys, new-vals}) Closures are your friend:
(defn map-assoc-if-kv
[coll k v & new-kvs]
(for [m coll]
(if (= (k m) v)
(apply assoc m new-kvs)
m)))
(map-assoc-if-kv
[{:id 1 :hax 5}
{:id 2 :hax 3}
{:id 7 :hax 9}]
:hax 9
:top "sweet"
:bottom "nice"
:side "yuk")
;;=> ({:id 1, :hax 5} {:id 2, :hax 3} {:id 7, :hax 9, :top "sweet", :bottom "nice", :side "yuk"})
Which you can rewrite using map
like so:
(defn map-assoc-if-kv
[coll k v & new-kvs]
(map
#(if (= (k %) v)
(apply assoc % new-kvs)
%)
coll))
So you see, you don't need to pass the new-kvs and the k and v to compare against to the function used for mapping, instead you can have the function close over them using closures.For composability, it might be better to separate the mapping with the assoc logic though like:
(defn assoc-if-kv
[m k v & new-kvs]
(if (= (k m) v)
(apply assoc m new-kvs)
m))
(map
#(assoc-if-kv % :hax 9 :top "sweet" :bottom "nice")
[{:id 1 :hax 5}
{:id 2 :hax 3}
{:id 7 :hax 9}])
Sweet! 😄 thank you @U0K064KQV and thanks for going from for
to map
that's really nice to see the side-by-side