Fork me on GitHub
#clojure-europe
<
2020-10-23
>
slipset06:10:25

God morgen. While working on deps-deploy yesterday, I had to do some RTFSing in pomegranate https://github.com/clj-commons/pomegranate/blob/master/src/main/clojure/cemerick/pomegranate/aether.clj and I'm struck by how different this code is from the code that I write on a day to day basis. From a call-site perspective, kw-args look realy nice:

(aether/deploy :artifact-map artifact-map
                 :repository repository
                 :coordinates coordinates)
But reading the code that gets called, not my cup of tea.

ordnungswidrig12:10:35

I like kw-args when you need to do what they call “props drilling” in js react: passing down the same arguments deeply into the callstack and only change part of it.

ordnungswidrig12:10:09

But I’d rather use a map as an argument which makes everything simpler: (aether/deploy {:a… coordinates})

slipset13:10:11

Yes, I totally see the use case for sending a bunch of stuff in one go, but as you, I prefer doing that in a map rather than with kw-args. Don't remember who's law it is, but basically if your function takes 9 arguments, you probably forgot a couple. Might have been Perlis

slipset06:10:43

What's your preference:

(->> foos (map (fn [foo] [(bar foo) (baz foo)])) (into {}))
or
(reduce (fn [foos foo] (assoc foos (bar foo) (baz foo))) {} foos)

plexus07:10:23

(into {} (map (juxt bar baz)) foos)

👍 3
borkdude07:10:02

I have a slight preference to put into at the front and yes, juxt, thank you plex!

plexus07:10:55

I'm a big into + map + juxt fan 🙂

👍 6
slipset07:10:06

Yeah, didn't see the use for juxt here as this was a simplification of a more complex example, but still... Maybe worth thinking how this could be solved with juxt since it probably leads to smaller building blocks

borkdude07:10:00

@slipset but there's a law that when you have a chance to use juxt, you should take it. unlike macros

❤️ 3
slipset07:10:40

Which law is that? Borkdudes law? 🙂

borkdude07:10:48

clj-kondo: warning: this program doesn't use juxt

👍 9
dominicm07:10:41

literally unusable

dharrigan07:10:33

Is there a way to lint that would suggest juxt, i.e., given the example above?

dharrigan07:10:47

(I'm still learning when I can use it)

slipset07:10:30

I guess it's something that bikeshed or kibitz could do, since they look at patterns

slipset08:10:07

So if you can write a pattern that expresses when to use juxt, then you're good.

otfrom08:10:03

I'm almost always using juxt to turn maps into ordered vectors of the values in maps

slipset09:10:00

And in my example, it gets used to create map entries from vectors so we can stick them in a map

slipset12:10:28

Oh, I just need to get this off my chest. Wouldn't it be nice if group-by could get an extra arity so you could avoid doing somehting like:

(->> foos
   (group-by :id)
   (medley/map-vals whatever))
and rather do
(group-by :id whatever foos)

slipset12:10:55

(somewhat inspired by javas grouping collector (or whatever it's called))

otfrom12:10:09

I've just gotten really used to writing

(map (fn [[k v]]
  [k (do-sommat-to-v v)])

otfrom12:10:28

and just don't use map-vals

otfrom12:10:47

grouped-map is a bit different. It looks a bit like by-key from cgrand/xforms

otfrom12:10:17

which is perhaps a bit more general as it applies a comped transducer to the groups (does the same for partition)

dominicm14:10:42

@slipset You mean (zipmap (map :id foos) (map whatever foos))

slipset14:10:02

Not if whatever removes :id

dominicm14:10:33

@slipset I'm confused. What's an example use-case?

slipset15:10:12

Might not be realistic, but let’s try:

slipset15:10:59

(def users [{:age :best, :name "erik"}, {:age :best, :name "dominic"}, {:age :too-old :name "bruce"])
(grouped-map :age :name users)
=> {:best ["erik", "dominic"], :too-old ["bruce"]}     

dominicm15:10:36

Oh I see, you want to keep the sequence

borkdude15:10:42

@slipset This is just a matter of generalizing group-by right?

(defn group-by2
  [f coll f-combine empty f-elt]
  (persistent!
   (reduce
    (fn [ret x]
      (let [k (f x)]
        (assoc! ret k (f-combine (get ret k empty) (f-elt x)))))
    (transient {}) coll)))
#'user/group-by2

user=> (group-by2 :age users conj [] :name)
{:best ["erik" "dominic"], :too-old ["bruce"]}

ordnungswidrig15:10:40

You could drop the empty argument and have the combine function defaul accordingly.

ordnungswidrig15:10:59

(e.g. (fnil conj []) in this case)

borkdude15:10:00

you mean like a 0-arg?

borkdude15:10:09

@ordnungswidrig on second thought no, because you can have nil vals:

user=> (get {:a nil} :a :foo)
nil

borkdude15:10:34

hmm, but ret is our own map

borkdude15:10:37

so never mind

ordnungswidrig15:10:21

Otoh you can always thread the result of a simple group-by through a combine function

dominicm15:10:12

xforms has this, as @otfrom wisely mentioned :)

ordnungswidrig15:10:39

(->> x (group-by :some-key) (map (fn [[k vs] (reduce + vs)) (into {}))

ordnungswidrig15:10:53

(Somebody who knows about transducers can rewrite this to be performant I guess)

ordnungswidrig15:10:01

((But that’s not the point))

borkdude15:10:35

there's also index-by from medley which I sometimes need. which can also be written using the very decomplected group-by2:

(def users [{:age :best, :name "erik"}, {:age :best, :name "dominic"}, {:age :too-old :name "bruce"}])

(defn group-by2
  [group-fn combine-fn empty elt-fn coll]
  (persistent!
   (reduce
    (fn [ret x]
      (let [k (group-fn x)]
        (assoc! ret k (combine-fn (get ret k empty) (elt-fn x)))))
    (transient {}) coll)))

(prn (group-by2 :age conj [] :name users))
;;=> {:best ["erik" "dominic"], :too-old ["bruce"]}

(defn index-by [f coll]
  (group-by2 f (fn [_ x] x) nil identity coll))

(prn (index-by :id [{:id 1 :foo 2} {:id 2 :foo 2}]))
;;=> {1 {:id 1, :foo 2}, 2 {:id 2, :foo 2}}

ordnungswidrig19:10:45

(fn [_ x] x) => second 😛

borkdude19:10:28

not really though?

borkdude19:10:44

in lambda calculus it's the expression for false I believe

dominicm21:10:47

Tools namespace, why must you bother me so?

dominicm21:10:58

I have half a mind to abandon it

dominicm21:10:40

TNS-45 has pestered every clojurescript project I've ever been on. As of yet there's no general purpose solution. I'm very tempted to look at alternatives.