Fork me on GitHub
#beginners
<
2018-11-02
>
TK02:11:49

Can I use threading macro expecting two arities on the first function? Something like this

(->> type coll
     (filter-coll-by-type)
     (map-to-get-only-one-attribute)
     (join-into-string)
     ...)
the filter-coll-by-type function would receive the coll and the type to filter.

TK02:11:04

or is it better to filter before and then use the filtered collection on the threading macro?

TK02:11:59

-- I was taking a look at the docs (https://clojuredocs.org/clojure.core/-%3E%3E), and found something that would work for me:

(defn get-total
  [coll m]
  (->>
   (get-result coll m)
   (map #(:val %))
   (reduce +)))
Use a function "scope" to have the coll and type

chrisulloa02:11:43

What about just (->> coll (filter-coll-by-type type) (map-to-get-only-one-attribute) (join-into-string) ...) ?

TK02:11:50

thanks @christian.gonzalez! it works for me! I made a PR with it: https://github.com/LeandroTk/pokemon/pull/1 I'm just learning it and I don't if it is the best practice to use it that way. But thanks 🙂

andy.fingerhut02:11:50

It is very common to use threading macros where the functions in the 'pipeline' have multiple arguments.

kenran_06:11:52

Is there a function that does the following: "zip" two seqs with a "gluing" function? For instance, (glue (fn [x y] (= (:id x) (:foo y))) xs ys) would then pair elements where :id and :foo are identical.

andy.fingerhut06:11:26

what result are you hoping to get if more than one element of xs has the same value of :id equal to BLARG, and more than one element of ys has the same value of :foo equal to BLARG?

andy.fingerhut06:11:11

If there were 5 elements in xs like that, and 10 elements in ys like that, do you want 5x10=50 pairs in the return value?

andy.fingerhut06:11:14

There are some similarities to what you asked about to clojure.set/join, but it probably isn't exactly what you want.

andy.fingerhut06:11:38

Doing a (group-by :id xs) and (group-by :foo ys) will give you two maps with keys equal to the values of those functions on xs and ys, and you could then merge-with them together with a custom merge fn.

kenran_07:11:02

Thanks, those suggestions were very helpful. My situation is a bit different, but reflecting about it with the above in mind led me to another solution: In my case I have a collection of id's and another collection containing items with id's (that needn't be in my first collection). But now I can either use the group-by approach or just filter first and then do what I want to do:

andy.fingerhut07:11:12

Nice. group-by is a good tool to have in the mental toolbelt.

kenran_07:11:23

I already use it a lot, but for some reason I didn't even remotely think of it this time 🙂

kenran_07:11:12

Now I have a disgusting huge block of code that starts with (map (fn [id] (when-let [...] (let [...] (when-let [...] (if-let [...]. Is there a hint on what I can do to simplify such stacked whens and lets (such that I essentially return nil whenever a thing does not exist, but still need the name of the thing if it exists)?

schmee07:11:20

@johb.maier check out better-cond (https://github.com/Engelberg/better-cond#minimizing-rightward-drift) for something that sticks close to clojure.core, or promenade if you want to try something a bit more different (https://github.com/kumarshantanu/promenade/blob/master/doc/intro.md#a-simple-example)

jaawerth07:11:12

ooh that's nice. I'm glad I'm not asleep yet so I got to see that

jaawerth07:11:59

@johb.maier it's not as flexible as the above but some->/`some->>` can help there, combined with as-> if you need to refer back to the original at any point in the pipeline. more limited but it can also get the job done depending on what you're doing

kenran_07:11:50

@schmee That looks great, thanks. I will speak with my colleague on Monday to see if we will use this in the project.

schmee07:11:52

just to note if you are reluctant to add dependencies is that both these projects have 0 dependencies of their own

kenran_07:11:03

@jesse.wertheim Ah yes, I tried it, but didn't get a nice result (I need many names, so I can't really thread much through any some-> without an as->).

kenran_07:11:01

I might be overlooking something though. What I take away from @schmee's proposed libraries is at least that such code can happen and isn't always easily simplifyable.

jaawerth07:11:22

yeah it gets unwieldy again if you need the names a bunch of times throughout the pipeline, can't do things like (some-> {:b 2 :c 3 :d 4} (as-> m (or (:a m) (:b m))) (* 10))

jaawerth07:11:14

it can definitely happen and it definitely gets annoying

jaawerth07:11:26

@johb.maier we might be able to help clean it up if you want to share a snippet though. are you trying to remove items from the collection of id's based on whether the second collection has items with those ids, or..?

kenran_07:11:48

Let me see if I can break it down so that it's actually readable from the outside 🙂

kenran_08:11:40

This is the gist of it I think. I hope the parens are alright.

kenran_08:11:15

I feel like the first when-let is the most problematic thing. I basically only want to map over those ids that I have items in all-items for (the names are bogus in the snippet).

kenran_08:11:59

And the (first (filter ... in that line looks very bad to me as well.

schmee08:11:58

if you rewrite that with better-cond it will look just fine I think, give it a shot and see what you think

schmee08:11:14

you can get rid of the let by doing (:foo ..) in the first when-let

jaawerth08:11:13

@johb.maier what's item-2? the first item from the next id?

schmee08:11:23

user=> (:foo nil)
nil

kenran_08:11:31

@jesse.wertheim Oh, it comes from way above. Something that's also either there or nil.

kenran_08:11:22

@schmee Sorry I wasn't more specific: the name bindings I created are all used later on (no getting rid of those names) in the ... part.

kenran_08:11:04

But yes, I'm pretty sure it will look great with better-cond. I will try that Monday. It's good to know that it has no external dependencies.

kenran_08:11:57

I have an idea. I could do (map (fn [item] ...) loaded-items, where loaded-items are the items whose id's are present in all-items.

kenran_08:11:16

The resulting seq will contain fewer nil values, but that doesn't matter here.

lassemaatta08:11:19

this might be a horrible idea, but perhaps you might "transpose" the flow of data. Instead of mapping over the selected-ids, you might consider something like (->> all-items, (filter #(contains? selected-ids (:id %)), (do-something-with-selected-items), ...)?

kenran_08:11:30

@lasse.maatta I will think about it! It looks great flow-wise, but I guess I will miss the bindings in the last call.

kenran_08:11:48

Thanks for all the great input guys!

jaawerth08:11:44

@johb.maier a lot of this can be cleaned up using these functions as transducer pipelines instead of seq functions too, like combining map and filter. and here's a custom distinct-by I've used before - it's just distinct that tracks uniqueness by the result of a predicate https://repl.it/@jaawerth/distinct-by

jaawerth08:11:26

@johb.maier that'll give you the first item for every ID, and then you can just compose it right with the map function, as I did in that example

jaawerth08:11:36

the key to using it being lines 27-30 (the rest is ugly stuff just to set up the contrived example)

kenran_08:11:19

Transducers are not really on my radar yet. Does the above mean that map and filter are "combined" first before being let loose on a seq?

kenran_08:11:57

The distinct-by looks great for my example, thanks! I learned a lot today.

jaawerth08:11:20

when you compose them, they turn into a single transducer - basically the map is passing its rf (reduce funciton) to that of distinct-by which is using it to supply values to map without ever having an intermediate structure

jaawerth08:11:24

(this also makes it more performant)

jaawerth08:11:09

you can't use the composed transducers on their own in that form - you would then use into, sequence, or (in the rare case you don't want any caching of results) eduction

jaawerth08:11:55

(into [] (comp xf1 xf2 xdf3) some-col) is like doing (->> some-col (seq-fn1) (seq-fn2) (seq-fn3) (into []))`, but without all the wasted overhead on the lazy sequences

jaawerth08:11:04

also cleaner, IMO. and no problem!