Fork me on GitHub
#beginners
<
2019-11-11
>
Marcio Arakaki02:11:42

Hi Is there an easy whey to group the result of a set of functions into an array ? What I want: (deffn validate [param1 param2 param3] (func1 (validationFunc1 param1) (validationFunc2 param2) (validationFunc3 param1 param2 param3)......)) (validate p1 p2 p3) => [validationFunc1Result validationFunc2Result validationFunc3Result] I'm using juxt right now but the fact that I need to provide all paramters to all functions is making my code unmaintenable

seancorfield02:11:28

If your functions take different arguments, there's not going to be anything simpler than using juxt with wrappers like I suggested the other day @marcio.akk

Marcio Arakaki02:11:19

I was considering (let [array []] (conj array (validationFunc1 param1) (validationFunc2 param2) (validationFunc3 param1 param2 param3)......) )

seancorfield02:11:48

or just [ (validationFunc1 param1) (validationFunc2 param2) (validationFunc3 param1 param2 param3) ... ]

Marcio Arakaki02:11:00

@seancorfield Perfect!

jaihindhreddy05:11:30

You can even give them names instead of relying on order in the vector. Will help in maintenance. {:a (validation-fn-a param1) :b (validation-fn-b param1 param2)}

Meepu12:11:20

There must be a prettier way to do this. Original code was (defn get-exam-by-uuid [uuid] (map dissoc-timestamps (repository/get-one-by-uuid uuid))) I needed to run it through one more function, so it became (defn get-exam-by-uuid [uuid] (map add-is-open (map dissoc-timestamps (repository/get-one-by-uuid uuid)))) It works, but is there a better way to apply two functions over a collection than nested maps?

hansbugge12:11:22

Function composition: comp

user=> (map inc (map #(* % 3) [1 2 3]))
(4 7 10)
user=> (map (comp inc #(* % 3)) [1 2 3])
(4 7 10)

Meepu13:11:30

But unfortunately doesn't work in my case. I get "Don't know how to create ISeq from: clojure.core$map$fn__5847".

Meepu13:11:23

The major problem here is that I don't know how to debug this code to find out what repository/get-one-by-uuid is returning.

didibus17:11:45

Are you using a REPL?

didibus17:11:08

If so, you can just run that line

didibus17:11:23

Add a few println statements if you need too

didibus17:11:41

If using Cider or Cursive, you can also use their respective debugger

AlesH23:11:23

Personally I prefer threading macros for this:

(defn get-exam-by-uuid [uuid]
  (->> (repository/get-one-by-uuid uuid)
      (map dissoc-timestamps)
      (map add-is-open ))

Jcaw13:11:43

Try a lambda?

Jcaw13:11:04

(fn [item] (fn2 (fn1 item)))

Meepu13:11:59

Lambda gives the same error.

Meepu13:11:45

(map f1 (map f2 (...)) still works.

Jcaw13:11:21

Paste what you're running?

henrik16:11:59

Keep in mind that comp evaluates right-to-left. Maybe your functions are being applied in the wrong order. https://clojuredocs.org/clojure.core/comp

Cas Shun17:11:19

if I have a vector of maps like [{:key1 val, :key2 val} {:key1 val, :key2 val} ...] and I want to turn it into a vector of maps containing a single key like [{:key 1 val} {:key1 val} ...], what would be the most idiomatic way to achieve this? I wrote this (map (fn [x] {:key1 (:key1 x)}) [{:key1 1 :key2 2} {:key1 1 :key2 2} {:key1 1 :key2 2}]) and it seems a bit goofy.

dpsutton18:11:52

map select-keys?

dpsutton18:11:20

or if you only want a single key, map doesn't help? (map :key1 coll)

Cas Shun18:11:12

@dpsutton I need the original shape but with only a single key

Cas Shun18:11:47

looks like select-keys does what I want

dpsutton18:11:53

right. i'm questioning if you actually have a need. but if you do actually need a map, then just use select-keys

Cas Shun18:11:26

yeah, the API I'm using requires that shape

Cas Shun18:11:08

ahh, that's beautiful, thank you πŸ™‚

walterl18:11:27

Is there a "prettier" (less verbose) way to do that than (map #(select-keys % [:key1]) vs)?

andy.fingerhut18:11:22

That is the most concise way to do it that I know.

andy.fingerhut18:11:13

Some people prefer to avoid the #() syntax for defining anonymous functions, and might instead prefer (map (fn [m] (select-keys m [:key1])) vs)

walterl18:11:49

Thanks for confirming. It's not the worst, but I'm clearly just not that used to reading such forms (yet) πŸ˜›

henrik19:11:16

You can break out the function in a let/`letfn` if you find it easier to read that way.

walterl20:11:56

Nah. If there's nothing like that in core, I agree that it'll add more effort than it's worth πŸ™‚

andy.fingerhut18:11:53

Or if that anonymous function is something they want to use multiple times, to give it a name and use the name there.

andy.fingerhut18:11:54

Some might even prefer to define that function with a name even if it is only used once -- kinda depends upon preference there -- it can be annoying to pick a reasonable name for something only used once.

walterl18:11:32

I guess I was hoping for a core utility like (map (at1 select-keys [:key1]) vs)

noisesmith18:11:56

another alternative: (map (fn [{v :key1}] {:key1 v}) vs)

noisesmith18:11:58

that's kind of weird, I guess, but it seems self describing in a way I like

andy.fingerhut18:11:07

Where at1 returns a function (fn [x] (select-keys m [:key1])) ? You could write at1 as a Clojure macro if you wanted.

andy.fingerhut18:11:32

Actually, in the case shown, it could be written as a Clojure function, too...

andy.fingerhut18:11:43

user=> (defn at1 [f & args]
  (fn [x]
    (apply f x args)))
#'user/at1
user=> (map (at1 select-keys [:a]) [{:a 1 :b 2} {:a 3 :b 4}])
({:a 1} {:a 3})

andy.fingerhut18:11:43

Creating anonymous functions are a 'core utility', and are more general than at1, and I think most Clojure programmers familiarize themselves with it in time.

ghadi18:11:57

yeah, I'd get used to it

walterl19:11:44

Noted. Thanks @U050ECB92, @U0CMVHBL2 thanks2

ghadi18:11:35

tiny little DSL-y utility functions add a lot of overhead -- mental overhead

ghadi18:11:12

#(select-keys % [:a]) is concise & readable

bfabry18:11:13

the specter for it is not bad

user=> (sp/select [sp/ALL (sp/submap [:a])] [{:a 1 :b 1} {:a 2 :b 2}])
[{:a 1} {:a 2}]

ghadi18:11:35

it's 100x worse than using clojure.core

Jason Kimmerling18:11:22

Could someone help me with a coding example from a book im reading? I need help dissecting what the reduce portion is actually doing, mainly the "let" portion is highly confusing for me lol.

(def order-details
  {:name "mitchard blimmons"
   :email ""})

(def order-details-validations
  {:name
   ["Please enter a name" not-empty]
   
   :email
   ["Please enter an email address" not-empty
    
    "Your email address doesn't look like an email address"
    #(or (empty? %) (re-seq #"@" %))]})

(defn error-messages-for
  "Return a seq of error messages"
  [to-validate message-validator-pairs]
  (map first (filter #(not ((second %) to-validate))
                     (partition 2 message-validator-pairs))))

(defn validate
  "Returns a map with a vector of errors for each key"
  [to-validate validations]
  (reduce (fn [errors validation]
            (let [[fieldname validation-check-groups] validation
                  value (get to-validate fieldname)
                  error-messages (error-messages-for value validation-check-groups)]
              (if (empty? error-messages)
                errors
                (assoc errors fieldname error-messages))))
          {}
          validations))

(validate order-details order-details-validations)

jaihindhreddy16:11:23

There are many ways to slice this kind of pie. I would structure this kind of thing slightly differently. Instead of the values in order-details-validators being vectors with alternating message and predicate, you could instead treat a map with the keys :msg and :valid? as a validator and store a vector of these validators as values in that map. Like this:

(def order-details
  {:name "mitchard blimmons"
   :email ""})

(def order-details-validations
  {:name [{:msg "Please enter a name" :valid? not-empty}]
   :email [{:msg "Please enter an email address" :valid? not-empty}
           {:msg "Your email address doesn't look like an email address"
            :valid? #(or (empty? %) (re-seq #"@" %))}]})
You could then define a function that just applies one validator to a given value returning the error message if invalid. Like this:
(defn apply-validator
  "Applies a validator to a value and returns an error message if invalid
  and nil if valid"
  [value {:keys [msg valid?]}]
  (when-not (valid? value) msg))
The implementation of validate no longer require a raw reduce. You can just take the data for which you have validators (using select-keys), merge it with the validators map through a function that applies all the validators you have (using merge-with), then keep the stuff which is invalid (using remove) and pour it into a map (using into). Like this:
(defn validate
  "Returns a map with a vector of errors for each key"
  [m validations]
  (->> (select-keys m (keys validations))
       (merge-with #(keep (partial apply-validator %2) %) validations)
       (remove (comp empty? val))
       (into {})))
Your original call works as it is:
(validate order-details order-details-validations)
An advantage of doing it this way is, you can add more keys to your validators without breaking anything.

jaihindhreddy16:11:48

^ Sorry for being so wordy πŸ˜…

seancorfield18:11:01

This piece is destructuring validation to get at the first two elements of the sequence/vector:

(let [[fieldname validation-check-groups] validation
so it's like (let [fieldname (first validation) validation-check-groups (second validation) ,,, -- does that help @jkimmerling

seancorfield18:11:39

And the reduce is relying on treating a hash map (`order-details-validations`) as a sequence -- which has pairs of [key value] [key value] (whereas the map is {key value key value}

seancorfield18:11:14

It would be more idiomatic (and probably less confusing?) to use reduce-kv here:

(reduce-kv (fn [errors fieldname validation-check-groups]
              (let [value (get to-validate fieldname)
                    error-messages (error-messages-for value validation-check-groups)]
,,,

Jason Kimmerling18:11:54

@seancorfield that did help quite a bit, thank you!

ghadi19:11:30

@bfabry I'm serious. spectre (spelling?) adds complexity on top of what is essentially a function call

ghadi19:11:10

it's its own DSL, an additional dependency, and most people (certainly most of #beginners) don't know it

bfabry19:11:38

for the most part I agree with you, I just don't think it's great for the community to express that kind of technical argument the way you did

bfabry19:11:12

specter is a load of extra complexity to add to a team/project. unless you happen to be on a team/project that's already happy with it

ghadi19:11:15

fair enough. (I'm still not sold on the value prop for spectre)

seancorfield20:11:47

I can accept that Specter might be "great" for complex, nested data transforms but for a simple case like this it is a) total overkill b) harder to read than all of the core-based suggestions folks made and c) is a whole new learning curve for a declarative (and not very Clojure-y) syntax of its own.

seancorfield20:11:21

I don't think I've seen a single suggested use of Specter that is directly easier to read than the equivalent core functions so far. Certainly in this channel.

alex31415920:11:29

Hi there, please is there a way to temporarily stop logging? Using tools.logging with log back-classic backend, I’m running simulations on a schedule and my log files are getting enormous. Thanks!

ataggart20:11:27

What do you mean by temporarily?

ataggart20:11:04

Maybe just:

(binding [clojure.tools.logging/*logger-factory*
          clojure.tools.logging.impl/disabled-logger-factory]
  (do-stuff))

alex31415920:11:18

Hi Alex thanks - what I’m trying to do is when my simulation starts I disable all logging, and reenable it at the end. Alternatively disable all logging for all threads started by the simulation trade.

ataggart20:11:03

ok, the example above should suffice

alex31415920:11:30

Let me try thanks a lot!!

alex31415921:11:45

Worked a charm! Thanks so much for the quick answer!

skykanin20:11:09

Why does the first expression here not work?

ataggart20:11:41

Java methods are not functions. The Math/abs is syntactic sugar around the . special form. See https://clojure.org/reference/java_interop

noisesmith21:11:37

not only that, but also methods are not first class Objects, so cannot be pushed to the stack, so they cannot be provided as arguments

SoV421:11:57

For this particular project there will be something like 300 lessons that we're making. What's a decent way to store lots of maps, a vector with index for each collection of lesson maps?

SoV421:11:28

lots of lessons, each lesson has a vec of maps... with many vecs of maps, what is a smart way to index ?

Cas Shun23:11:55

is there a core function that will take a collection, apply a pred to each item, and produce a collection of something like ([truthy-stuff] [falsy-stuff])? Essentially filter that gives you the result and its complement.

Cas Shun23:11:48

reduce seems fairly simple, but I'm curious if there's a better option

zlrth23:11:35

in a project.clj, can i have as :resource-paths all directories except one? i’d like to lein uberjar everything in resources/* except one directory that has historically had large files don’t need to be in the jar