Fork me on GitHub
#beginners
<
2023-12-19
>
Matthew Twomey16:12:06

I dislike this (that I wrote):

(defn calibration-update [cali-idx & {:keys [k-value n-coef]}]
  (let [cali (->> @state :extrusion-cali
                  (filter #(= (:cali_idx %) cali-idx)) first
                  (#(if k-value (assoc % :k_value k-value) %))
                  (#(if n-coef (assoc % :n_coef n-coef) %)))]
    cali
    ;; Do stuff with cali
    ))
I feel like I’m missing a cleaner / clearer way to accomplish this “optional replacement”. The idea is to optionally replace values based on what the function is called with. An example starting value for extrusion-cali is:
{:cali_idx 4770
 :filament_id "GFL99"
 :k_value "0.040000"
 :n_coef "1.000000"
 :name "Protopasta PLA"
 :setting_id "GFSL99"}
How would you all do this?

nikolavojicic16:12:29

Add assoc-some util method. Or use cond->.

Matthew Twomey16:12:47

I actually do have an assoc-if method I wrote long ago, but I wanted to check here if there was something more idiomatic. I didn’t think about cond->, looking….

ghadi16:12:48

nitpick: don't mix anonymous fns with threading macros

ghadi16:12:19

e.g. (->> coll (#(....)) is not idiomatic

Matthew Twomey16:12:12

I’ve heard that before. I just don’t find it less readable than fn, but I know a lot of people do.

ghadi16:12:24

also avoid changing between collection ops and scalar ops in a ->> macro

ghadi16:12:44

it's easy to miss the call to first on the second line of the thread

Matthew Twomey16:12:59

can you suggest how you’d split this out?

ghadi16:12:19

add more bindings to the let

ghadi16:12:08

it's not a goal to coagulate everything into a ->>, it has some downsides

ghadi16:12:00

one downside is that forms are not named, so you gotta read all the recipe

ghadi16:12:55

also don't reach into a global atom inside your fns

ghadi16:12:05

pass arguments, it's testable

ghadi16:12:38

(defn calibration-update [cali-idx & {:keys [k-value n-coef]}]
  (let [[extrusion] (->> (:extrusion-cali @state)
                         (filter #(= (:cali_idx %) cali-idx)))]
    (cond-> extrusion
      k-value (assoc :k_value k-value)
      n-coef (assoc :n_coef n-coef))))

👍 1
ghadi16:12:53

but I recommend passing the value of @state explicitly to this fn

seancorfield16:12:58

(-> @state 
    :extrusion-cali
    (->> (filter ..))
    first
    (cond-> 
      k-value (assoc :k_value k-value)
      n-coef  (assoc :n_coef  n-coef)))
But as Ghadi says, I'd probably pull the first piece into a let and name it.

👍 1
Matthew Twomey16:12:57

How about this?:

(defn calibration-update [cali-idx & {:keys [k-value n-coef]}]
  (let [calibrations (:extrusion-cali @state)
        target-calibration (first (filter #(= (:cali_idx %) cali-idx)
                                    calibrations))
        target-calibration-updated (cond-> target-calibration
                                     k-value (assoc :k_value k-value)
                                     n-coef (assoc :n_coef n-coef))]
    target-calibration-updated
    ;; Do stuff with cali
  ))

ghadi16:12:18

that's better

seancorfield16:12:54

(and def. do not put multiple calls on a single line -- I totally missed first until I saw Ghadi's destructuring and I only barely noticed :extrusion-cali)

Matthew Twomey17:12:25

@U04V70XH6 you mean when threading yeah?

seancorfield17:12:50

Yup. Either all on one line or all vertical -- don't mix them.

1
Matthew Twomey17:12:50

Ok, I like where this ended up, I’m glad I asked 👍. Thanks guys, learned some new stuff.

1
Matthew Twomey17:12:08

@U050ECB92 “also don’t reach into a global atom inside your fns” Can you elaborate on this a little? It seems like ultimately I’m going to have to access it from a function if I want the current state?

seancorfield17:12:33

Pass the state as an argument, don't use a global, is the general advice.

Matthew Twomey17:12:07

I’m confused there - because then function that is calling this function, is then accessing this atom from inside a function.

Matthew Twomey17:12:32

(e.g. no matter what, I’m accessing this atom from inside a function)

seancorfield17:12:35

You would need to pass the "system state" through your entire call chain from -main essentially.

seancorfield17:12:02

Makes it easier to test everything.

Matthew Twomey17:12:13

Just limit where we’re doing it.

seancorfield17:12:19

And makes it clear that the functions depend on state.

Matthew Twomey17:12:22

In this case it’s a little trickier because the state is constantly being updated by another thread, but I understand what you are saying.

seancorfield17:12:23

Pass the atom around, in general, and deref it in arguments to functions that only read its value perhaps?

Matthew Twomey17:12:48

Yeah ok - more food for thought there, thank you. I will review.

seancorfield17:12:22

That also removes the possibility of a function that does @state in two places getting two different values -- except where you specifically need that to happen.

Matthew Twomey17:12:48

Yeah makes sense

seancorfield17:12:37

(including two places in a single call chain -- so if f does @state and calls g which also does @state and they end up operating on two different states... that sort of thing would be very hard to debug)

Matthew Twomey17:12:03

Yeah I could see that. I don’t have control over when it’s updating - but I can make sure an entire given call chain is operating on a single fixed value. In this specific case, this function is the entire call chain (well, it has a cli-parent function that invokes it), but I see the potential problem.