clojure-europe

For people in Europe... or elsewhere... UGT https://indieweb.org/Universal_Greeting_Time
slipset 2020-10-23T06:18:25.175400Z

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.

ordnungswidrig 2020-10-23T12:30:35.189Z

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.

ordnungswidrig 2020-10-23T12:31:09.189200Z

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

slipset 2020-10-23T13:01:11.191900Z

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

slipset 2020-10-23T06:46:43.176400Z

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)

plexus 2020-10-23T07:00:23.176900Z

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

👍 1
borkdude 2020-10-23T07:01:02.178Z

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

plexus 2020-10-23T07:01:55.178400Z

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

👍 2
slipset 2020-10-23T07:26:06.180Z

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

borkdude 2020-10-23T07:36:00.180800Z

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

❤️ 1
slipset 2020-10-23T07:36:40.181300Z

Which law is that? Borkdudes law? 🙂

borkdude 2020-10-23T07:38:48.181700Z

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

👍 3
dominicm 2020-10-23T07:42:41.181800Z

literally unusable

dharrigan 2020-10-23T07:43:33.182200Z

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

dharrigan 2020-10-23T07:43:47.182500Z

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

slipset 2020-10-23T07:56:30.183300Z

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

slipset 2020-10-23T07:57:41.183500Z

https://github.com/jonase/kibit

dharrigan 2020-10-23T07:58:02.183800Z

Interesting

slipset 2020-10-23T08:00:07.184400Z

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

dharrigan 2020-10-23T08:01:44.184600Z

:thumbsup:

2020-10-23T08:18:03.185200Z

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

slipset 2020-10-23T09:43:00.185900Z

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

thomas 2020-10-23T09:50:01.186100Z

mogge

slipset 2020-10-23T12:18:28.187300Z

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)

slipset 2020-10-23T12:19:55.188100Z

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

slipset 2020-10-23T12:24:39.188300Z

Basically this thing here https://github.com/plumatic/plumbing/blob/master/src/plumbing/core.cljx#L164

ordnungswidrig 2020-10-23T12:27:32.188600Z

🐈

2020-10-23T12:40:09.189900Z

I've just gotten really used to writing

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

2020-10-23T12:40:28.190200Z

and just don't use map-vals

2020-10-23T12:44:47.190900Z

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

2020-10-23T12:45:17.191500Z

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

dominicm 2020-10-23T14:17:42.192100Z

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

slipset 2020-10-23T14:28:02.192900Z

Not if whatever removes :id

dominicm 2020-10-23T14:33:33.193Z

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

slipset 2020-10-23T15:03:12.193300Z

Might not be realistic, but let’s try:

slipset 2020-10-23T15:06:59.196700Z

(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"]}     

dominicm 2020-10-23T15:28:36.197300Z

Oh I see, you want to keep the sequence

borkdude 2020-10-23T15:29:42.197700Z

@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"]}

ordnungswidrig 2020-10-23T15:32:40.198300Z

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

ordnungswidrig 2020-10-23T15:32:59.198900Z

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

borkdude 2020-10-23T15:33:00.199Z

you mean like a 0-arg?

borkdude 2020-10-23T15:33:20.199200Z

yeah

borkdude 2020-10-23T15:36:09.199600Z

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

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

ordnungswidrig 2020-10-23T15:36:30.199900Z

good point!

borkdude 2020-10-23T15:36:34.200200Z

hmm, but ret is our own map

borkdude 2020-10-23T15:36:37.200400Z

so never mind

ordnungswidrig 2020-10-23T15:36:43.200700Z

true

ordnungswidrig 2020-10-23T15:37:21.201400Z

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

dominicm 2020-10-23T15:39:12.204200Z

xforms has this, as @otfrom wisely mentioned :)

ordnungswidrig 2020-10-23T15:39:39.204800Z

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

ordnungswidrig 2020-10-23T15:39:53.205200Z

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

ordnungswidrig 2020-10-23T15:40:01.205400Z

((But that’s not the point))

borkdude 2020-10-23T15:43:35.206400Z

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

ordnungswidrig 2020-10-23T19:01:45.207Z

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

borkdude 2020-10-23T19:03:28.207200Z

not really though?

borkdude 2020-10-23T19:04:44.207700Z

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

ordnungswidrig 2020-10-23T19:26:22.207900Z

hmmm

ordnungswidrig 2020-10-23T19:26:31.208100Z

actually true!

dominicm 2020-10-23T21:01:47.208600Z

Tools namespace, why must you bother me so?

dominicm 2020-10-23T21:01:58.209Z

I have half a mind to abandon it

dominicm 2020-10-23T21:02:40.210100Z

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.