Fork me on GitHub
#beginners
<
2018-09-11
>
john00:09:34

Seems like data/objects are more so isy, whereas functions/verbs are more about the usage, right?

john00:09:51

Though I guess in a way a verb is what it does

emilaasa06:09:59

Hi! I'm working with a sequence of maps that contain other maps and I want to map some of the nested values into a flat table. The code looks like this:

(clojure.pprint/print-table
  (->> failed-jobs
       (map #(select-keys % [:name :duration :id :runner]))))
The runner key holds a map where I'm only interested in the runners name. How do I map that into my flat structure?

schmee06:09:14

can you post a sample of expected input/output? that makes it easier to understand what’s going on (at least for me 😄 )

emilaasa07:09:53

Input:

{:name "job name"
:{runner {:description "runner description"}
:duration "111.1"
}
;; And a LOT of unwanted keys
Desired output:
"job name", "runner description", "111.1"

sundarj07:09:21

@emilaasa you can use (juxt :name (comp :description :runner) :duration)

Timo Freiberg10:09:48

i have a question 🙂 i need to convert a seq of key-value tuples into a map of key to value-seq/a multimap (keys are not unique) so far, i have the following code:

(def key-val-seq [[1 :a] [1 :b] [2 :c] [3 :d] [4 :e] [4 :f]])

(->> key-val-seq
     (group-by first)
     (map (fn [[k v]] [k (map second v)]))
     (into {}))
;; => {1 (:a :b), 2 (:c), 3 (:d), 4 (:e :f)}
it works, but i don't really find it elegant

Timo Freiberg10:09:58

any suggestions how to improve this?

borkdude10:09:16

@timo.freiberg I think it’s already fine. reduce-kv also works:

(->> key-val-seq
      (group-by first)
      (reduce-kv
       (fn [acc k v]
         (assoc acc k (mapv second v)))
       {}))

Timo Freiberg13:09:42

thanks, I didn't know about reduce-kv!

mg11:09:45

I'd probably just do something like:

(reduce (fn [m [k v]] (update m k conj v)) {} key-val-seq)

👍 4
mg11:09:05

Oh actually that's wrong

mg11:09:23

Or if you need to preserve the original value order in the map vals, replace conj with (fnil conj [])

👍 12
Denis G12:09:25

@michael.gaare wow. Didn't know about fnil func. Was writing the update-or-default function and using it 😅

mg12:09:11

Yep, I've done that before. The discovery of fnil (thanks to Stuart Sierra actually) was one of those golden moments

Denis G12:09:50

I was like, damn, writing if else is just not the way to do it 😄

joaohgomes00:09:27

4clojure is a nice resource to learn about the core functions.

Timo Freiberg13:09:06

very good recommendations from everyone 🙂

moo14:09:11

Would it be reasonable to think of .. as “thread first member access”

✔️ 4
borkdude14:09:54

nested member access

moo21:09:12

what’s the basic template for using loop and tail-recursion with an accumulator to process one element at a time?

moo21:09:57

I have this:

noisesmith21:09:03

usually reduce does that simpler - since it automates consuming a sequence

moo21:09:52

(def myitems ["one" "two" "three" "four"])
(loop [input myitems acc []]
    (when (not-empty input)
    (println acc)
    (recur (rest input) (conj (first input) acc))))

sundarj21:09:17

this loop works fyi, you just need to put the args to conj in the right order: (conj acc (first input))

moo21:09:02

I’m just trying to understand the basic pattern…

moo21:09:32

(that code doesn’t work…)

noisesmith21:09:34

(reduce (fn [acc el] (conj acc el)) [] myitems)

noisesmith21:09:44

or (reduce conj [] myitems)

noisesmith21:09:01

if you always consume in order, reduce is simpler

noisesmith21:09:47

your args to conj are reversed in that example, btw

moo21:09:28

so, if I want to do a bunch of things on each array element, I would use (do (things)) in the anon fn body?

noisesmith21:09:44

anon fns already have an implicit do

noisesmith21:09:09

as long as you use fn and not #()

moo21:09:18

so: (reduce (fn [acc el] (conj acc el) (do more here)) [] myitems)

noisesmith21:09:33

no - because the result of conj is being thrown away

moo21:09:51

oh, so wrap everything in let?

noisesmith21:09:54

(reduce (fn [acc el] ... (conj acc el)) [] myitems)

danny21:09:01

if i have a multimethod with a dispatch fn that returns a vector, like (defmulti foo (juxt :role :shirt-color)) (or something), how can i set the :hierarchy option if the values will come from two different hierarchies?

noisesmith21:09:02

if you need to reuse things use a let, sure

noisesmith21:09:49

your original is the way to do it with loop - if you fix the conj call

noisesmith21:09:01

loop is messier and it's nice to use something special purpose though

noisesmith21:09:33

or another way to put it is that there's more ways to make mistakes with loop

moo21:09:38

Regarding my loop. I’m working on brave and true ch3 prob5. I have this (which works):

(def asym-hobbit-body-parts [{:name "head" :size 3}
                             {:name "left-eye" :size 1}
                             {:name "left-ear" :size 1}) ; and so on.
(reduce (fn [acc part] 
        (let [n1 {:name (clojure.string/replace (:name part) #"^left-" "c1-")
             :size (:size part)}]
            (conj acc n1))) [] asym-hobbit-body-parts)
what’s a clojure-y way to basically rename #“^left-” to c1- through c5-?

moo21:09:00

do I wrap for around the let?

moo21:09:41

like: (for [x [1 2 3 4 5] (let [n1 … stuff (str “c” x “-”)

moo21:09:48

I have a sense that using for isn’t very functional…

hiredman21:09:58

oh no, for is totally functional

moo21:09:39

(reduce (fn [acc part] 
        (for [x [ 1 2 3 4 5]] 
            (let [n1 {:name (clojure.string/replace (:name part) #"^left-" (str "c" x))
                 :size (:size part)}]
                (conj acc n1)))) [] asym-hobbit-body-parts)
^ doesn’t work. I think I’m not returning the for output?

hiredman21:09:59

in C like languages for is a looping control structure, in clojure for is a list comprehension

jaawerth21:09:02

for returns a lazy-seq

hiredman21:09:24

with for you don't need the reduce

jaawerth21:09:04

@jm.moreau hint - you can use for to iterate+bind more than a single sequence

moo21:09:31

what I know is ugly is this :

(reduce (fn [acc part] 
        (let [n1 {:name (clojure.string/replace (:name part) #"^left-" "c1-")
             :size (:size part)}
             n2 {:name (clojure.string/replace (:name part) #"^left-" "c2-")
                  :size (:size part)}] ; and so on up to "c5-"
            (conj acc n1 n2))) [] asym-hobbit-body-parts)

noisesmith21:09:51

:size is not a valid binding name

moo21:09:26

@noisesmith it’s in a hashmap

noisesmith21:09:41

oh, so it's just bad indentation, I missed that

moo21:09:48

(it’s a key in hashmap) sorry 🙂

hiredman21:09:02

the whole thing almost certainly doesn't work

hiredman21:09:29

do you really intend to have two outputs in the list for every input?

moo21:09:58

yes, I want to take something like “left-foot” and create “c1-foot” “c2-foot” … “c5-foot”

moo21:09:16

for radially symmetrical monsters…

moo21:09:34

it’s just an exercise from brave and true.

hiredman21:09:55

ok, so what you have is a mapcat

moo21:09:00

but I’m trying to learn how to write idiomatically

hiredman21:09:03

(which for is great for)

hiredman21:09:12

but there is also mapcat

moo21:09:33

I’ll check that out. ty

jaawerth21:09:27

@jm.moreau as a further hint, try putting this in your REPL: (for [x ["a" "b" "c"], y (range 1 6)] [x y])

jaawerth21:09:04

I'd take a look at (doc for) while you're at it https://clojuredocs.org/clojure.core/for

jaawerth21:09:37

You could also, as hiredman is referring to, use mapcat

jaawerth21:09:57

but this will be cleaner to start with, I think

moo21:09:13

@jesse.wertheim thanks, yea. for makes sense and this would work:

(for [c (range 1 6)] (str "c" c "-"))
what trips me up is how to build up my accumulator and scoping

moo21:09:26

I’m used to “larger scopes” from OOP.

moo22:09:00

How do I build up an accumulator if I’m using for?

moo22:09:15

do I need an additional let to make another accumulator?

jaawerth22:09:22

for gives you a lazy sequence

noisesmith22:09:42

why would you need an accumulator for this?

jaawerth22:09:49

it looks like you're just conj'ing onto a list, so you shouldn't need one

jaawerth22:09:58

if you want you can turn the lazy-seq into a vector with vec

moo22:09:03

k lemme bash at the repl

noisesmith22:09:10

I proposed using reduce thinking an accumulator was actually needed, but if results are sequential in input order, use for or mapcat instead

moo22:09:28

so I can map on to my (for stuff)

moo22:09:34

b/c it’s a seq

noisesmith22:09:57

you don't need map over for, for is a list comprehension and can do that on its own

jaawerth22:09:19

yeah, I just mean you can feed it into map later if need be

noisesmith22:09:26

(or mapcat can also do this, without needing for)

jaawerth22:09:52

if you just want a pipeline with a final (vector, or other non-lazy) result, the optimized way would be into and mapcat

jaawerth22:09:05

if you just want a lazy-seq, mapcat or for will both work fine

Mario C.22:09:28

If I run lein cljsbuild once on my local machine. It builds it just fine with no warning or errors. but when Jenkins builds it using the same command I am getting a plethora of warnings and the build fails eventually.

moo22:09:03

I was thinking of for procedurally not as a lazy seq.

noisesmith22:09:29

yeah, the name can be misleading

jaawerth22:09:34

sure thing. yeah clojure's for is something of a revelation 😉

Mario C.22:09:56

Is there any reason this might be happening?

Mario C.22:09:08

Anyway to enable warnings on local cljs build?

hiredman22:09:18

so the assumption is: something is different when jenkins is building from when you are building

hiredman22:09:37

so you need to enumerate possible differences and check them

hiredman22:09:06

for example: do you have some plugin enabled that jenkins doesn't have? or do you have stale artifacts that are messing with the build? are you and jenkins actually building the same code? (different branches, uncommitted changes, etc)

hiredman22:09:51

my guess would be a stale artifact or a different branch, but that is just a wild guess

Mario C.22:09:21

I am building off the same branch

hiredman22:09:00

have you pushed all your changes? have you pulled any outstanding changes? is it the same commit?

hiredman22:09:36

does your jenkins build start out with a clear workspace or is it littered with the detritus of a previous build?

Mario C.22:09:39

I am under the assumption it is starting off with a clean workspace as the logs show it deletes the previous workspace

hiredman22:09:08

have you tried a clean build on a fresh checkout?

Mario C.22:09:53

what do you mean?

hiredman22:09:18

do a new checkout of the code and try to run cljsbuild

hiredman22:09:57

the idea being you could have some uncommitted change or some git ignored file or something in your checkout that is making the build pass

Mario C.22:09:39

Hmm weird running lein clean and then doing the lein cljsbuild once got it working

Mario C.22:09:49

but i did that previously and nothing changed

Mario C.22:09:25

what does lein clean do?

mfikes22:09:13

It generally removes stuff in your target directory (but I think it is configurable as to what it removes). The idea is to put your project back to a pristine state.

mfikes22:09:55

But, that may fail to repro what your CI system is doing, so as @hiredman suggests, it is best to clone into a new directory and try building from there.

mfikes22:09:20

Another place where you can get state that isn't cleaned up, even by cloning to a new tree is if you have :aot-cache enabled. https://clojurescript.org/reference/compiler-options#aot-cache

Mario C.22:09:30

hmm so it looks like lein clean was purely coincidental as to why the build started failing again on my local box

mfikes22:09:57

What general kinds of errors are you getting. (Did you say in the backlog?)

Mario C.22:09:38

The Jenkins build was failing as it should. Valid errors and all. But my local build was passing and I didn't (still don't) understand why

mfikes22:09:53

Ahh. Cool. Now at least you are cooking with fire.

mfikes22:09:08

Another source of things working on one box but not another if is the requires are wrong but just happen to work out by chance when :parallel-build is set to true

mfikes22:09:36

(Based on the random partial order it builds things in.)

mfikes22:09:15

But if you can deterministically repro now, then you are in good shape.

Mario C.22:09:59

Just ran ag on :parallel-build and no instance was found. So I guess it safe to say we are not using that

athomasoriginal23:09:28

Lets pretend that someone is using macOS and they want to install clojure CLI tools (not the JAR) without Homebrew. How would one go about doing this?

noisesmith23:09:13

the generic linux instructions should work

athomasoriginal23:09:30

Great. I wasn’t sure if there was something linux specific in there

noisesmith23:09:59

the shell script is pretty short so it is easy to verify - nothing I can see is linux specific if you have the prerequisite tools they list

noisesmith23:09:13

(bash, curl, rlwrap, java)

noisesmith23:09:01

oh you might also need gnu install - n/m I have it installed for some reason but osx comes with its own version

athomasoriginal23:09:44

I will give it a shot of a fresh macOS

noisesmith23:09:23

I think the gotchas will be rlwrap and java - not hard things to acquire