Fork me on GitHub
#beginners
<
2021-10-08
>
Lycheese08:10:31

When having the function below, is there any guarantee on the order the functions are executed? (Or are they even getting executed simultaneously?)

(defn test-func []
  {:content {:cat-a (func-a args)
             :cat-b (func-b args)
             :cat-c (func-c args)}})

yuhan08:10:03

There's no guarantee for map literals, even though it appears to be ordered for maps fewer than 8 elements due to ArrayMap implementation details. Try evaluating the following:

{1 (#(println 1))
 2 (#(println 2))
 3 (#(println 3))
 4 (#(println 4))
 5 (#(println 5))
 6 (#(println 6))
 7 (#(println 7))
 8 (#(println 8))
 9 (#(println 9))}

yuhan08:10:32

If you require a certain execution order, try constructing one using the hash-map function

Lycheese08:10:17

Ok so don't rely on the evaluation order of maps and if it's important use hash-maps, got it. Thank you.

noisesmith15:10:50

{} creates hash-maps too, the difference is that the order that the args to the hash-map function are evaluated is defined

Benjamin10:10:09

Is there a function that prints the argument and returns it? From cl I'm used to put print around something I want to log

yuhan10:10:43

(doto x println) :)

Robert Elliot11:10:02

Can loading a clojure file have side effects? I’m assuming calling clojure.core.load-file with untrusted content is unsafe without running in a security context or otherwise doing a bunch of validation to check?

delaguardo11:10:14

sure it can have side effects. load-file evaluates its content so things like defmethod or alter-var-root can change the context

Robert Elliot11:10:16

I assumed so, thanks.

andy.fingerhut09:10:55

Well, way more directly than defmethod or alter-var-root are things like creating connections and sending requests to servers, creating/deleting/renaming files, etc. executing arbitrary code.

Robert Elliot11:10:59

In the past when working in a compile time type-checked language on the JVM I’ve used a restricted security manager to prevent I/O when evaluating untrusted code that’s meant to be pure - see https://gist.github.com/Mahoney/8979e520b7477c9a9771fe3e1da3472a#file-ktsobjectloader-kt-L62-L70 . But I don’t know whether that would stop someone dynamically altering Clojure in a way that would allow them to inject dodgy code into some function in another namespace that would then be called outside the security manager’s context… guess some experimenting might be in order.

noisesmith15:10:09

this is a hard problem generally - there's some prior art in the clojurebot and lazybot tools which each provided an IRC sandbox clojure evaluation environment

noisesmith15:10:25

and yes, many (most?) of the security patches required were related to changing things in the clojure compiler or jvm core libs in order to change the behavior of future code

Adie13:10:15

(defn fetch-all-configs []
  (let [

        country-codes ["ID" "SG"]
        config-names [{:key "conf1" :value "conf1-mapping" }
                      {:key "conf2" :value "conf2-mapping"}
                      {:key "conf3" :value "conf3-mapping"}]
        all-configs []
        (for [country-code country-codes
              config-name config-names]
          (reduce all-configs (reduce #(flatten-configs %1 %2 (:key config-name) country-code) [] (get-full-config-by-config-name country-code (:value config-name) )))
          all-configs)
    ))

delaguardo13:10:32

I see wrong for using. It expects two arguments but there is three

Adie13:10:58

(defn fetch-all-configs []
  (let [

        country-codes ["ID" "SG"]
        config-names [{:key "conf1" :value "conf1-mapping" }
                      {:key "conf2" :value "conf2-mapping"}
                      {:key "conf3" :value "conf3-mapping"}]
        all-configs []
        (for [country-code country-codes
              config-name config-names]
          (into all-configs (reduce #(flatten-configs %1 %2 (:key config-name) country-code) [] (get-full-config-by-config-name country-code (:value config-name) ))))]
          all-configs)
    )

Adie13:10:32

will into append every response of (reduce #(flatten-configs %1 %2 (:key config-name) country-code) [] (get-full-config-by-config-name country-code (:value config-name) )) to all-configs

delaguardo13:10:38

looks like you expect into to mutate all-configs but it is immutable operation. it just returns new collection

delaguardo13:10:03

(defn fetch-all-configs []
  (let [country-codes ["ID" "SG"]
        config-names [{:key "conf1" :value "conf1-mapping" }
                      {:key "conf2" :value "conf2-mapping"}
                      {:key "conf3" :value "conf3-mapping"}]]
    (apply concat (for [country-code country-codes
                        config-name config-names]
                    (reduce #(flatten-configs %1 %2 (:key config-name) country-code) [] (get-full-config-by-config-name country-code (:value config-name)))))))
for returns a list of lists. and then apply concat will combine those lists into one

Adie13:10:43

does it also return that single list of all responses?

delaguardo13:10:57

(apply concat '((1 2 3) (4 5 6))) ;; => '(1 2 3 4 5 6)
I dont’ know much your code to answer that but here ^ is how concat works.

Adie13:10:41

Can somebody let me know if this code is correct

Adie13:10:01

Want to concat the result of (reduce #(flatten-configs %1 %2 (:key config-name) country-code) [] (get-full-config-by-config-name country-code (:value config-name) )) for each country-code and config-name pair in. all-configs list

V14:10:20

Is there a better way to to do this?

(defn add-status-to-metadata! [status]
  (let [object-metadata (ObjectMetadata.)]
    (.addUserMetadata object-metadata "status" status)
    object-metadata))
I use a java library to add some user metadata to an s3 object. I am trying create a new metadata object, add an entry to it and then return the metadata object with the status added (I believe its stored in a hashmap). This just seems weird to do in clojure. Is this really the way to go?

delaguardo14:10:33

(defn add-status-to-metadata! [status]
  (doto (ObjectMetadata.)
    (.addUserMetadata "status" status)))

🙌 1
Alex Miller (Clojure team)14:10:16

yep, but doto makes it feel less weird

Alex Miller (Clojure team)14:10:44

(doto (ObjectMetadata.)
  (.addUserMetadata "status" status))

Alex Miller (Clojure team)14:10:09

anytime you're doing Java interop with side-effecting functions, it's likely a good choice

V14:10:29

That definitely looks a lot better. I will remember that! Thank you very much

Alex Miller (Clojure team)14:10:57

it also highlights how weird side effecting functions on stateful objects really is :)

👍 2
Valentín14:10:06

Hi 👋 I made this simple SPA with re-frame, material-ui, and firebase. I'm new to clojure and clojurescript so if somebody want to take a look at the code, I'm open to recibe recommendations, better way to achive the solution, etc. Repo: https://github.com/vafer11/expenses Thanks.

kpav15:10:07

I am not experienced enough to give you any coding tips, but I did find a misspelling on the home page 🙂Don't have and account? Sign Up and should be an there

Eric Boatman14:10:05

Question: I'm working on a Sudoku Solver (highly recommend as a start project, I'm learning a lot). Since I'm using an atom for the "board", is it more appropriate to, when writing functions, to pass the "board" through the front of the function or write my function as if "board" is guaranteed?

thumbnail14:10:06

I'd go with pure functions (board-state as input, board-state as output). That way the atom is an "implementation detail" of the calling function.

2
Colin P. Hill15:10:22

^ Also makes it so that you’re only derefing the atom once per iteration, so if you compute a bunch of values off of it they’re all consistent with each other and only suffer the overhead of a single deref.

Alex Miller (Clojure team)15:10:25

in general, try to isolate the places where you touch state to as few places as possible - so only in the main loop that knows there is state (not in every function that touches the board)

Colin P. Hill15:10:03

Although, if it’s a solver and not an interactive game, I’m curious why it’s stateful at all.

Alex Miller (Clojure team)15:10:14

sometimes doing recursive algorithms over trees are easier to write if you hold state on the side (even if non-concurrent / interactive!)

1
Eric Boatman16:10:10

I appreciate the responses ... And I can tell I still have a lot to learn. I'm currently using an atom, so that I can watch my states carefully, and verify my "layers" of logic are working. Its possible when I'm done I'll realize I didn't need the crutch. I am leaving open the opportunity for converting this to an interactive. But not ready to open up that can of worms just yet. Currently just trying to get an easy puzzle solved.

Benjamin16:10:57

(require '[clojure.core.async :as a :refer [chan to-chan pipeline-blocking <!!]])

(defn blocking-operation [arg])

(let [concurrent 10
      output-chan (chan)
      input-coll (range 0 1000)]
  (pipeline-blocking concurrent
                     output-chan
                     (map blocking-operation)
                     (to-chan input-coll))
  (<!! (a/into [] output-chan)))
=> errs
Exception in thread "async-thread-macro-3" java.lang.AssertionError: Assert failed: (not (nil? itm))
	at clojure.core.async.impl.protocols$add_BANG_.invokeStatic(protocols.clj:40)
	at clojure.core.async.impl.protocols$add_BANG_.invoke(protocols.clj:37)
	at clojure.core$map$fn__5883$fn__5884.invoke(core.clj:2746)
	at clojure.core.async.impl.channels$chan$fn__6185.invoke(channels.clj:300)
	at clojure.core.async.impl.channels.ManyToManyChannel.put_BANG_(channels.clj:143)
	at clojure.core.async$fn__12343.invokeStatic(async.clj:172)
	at clojure.core.async$fn__12343.invoke(async.clj:164)
	at clojure.core.async$pipeline_STAR_$process__12547.invoke(async.clj:534)
	at clojure.core.async$pipeline_STAR_$fn__12559.invoke(async.clj:549)
	at clojure.core.async$thread_call$fn__12462.invoke(async.clj:484)
	at clojure.lang.AFn.run(AFn.java:22)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630)
	at java.base/java.lang.Thread.run(Thread.java:831)

Alex Miller (Clojure team)16:10:49

blocking-operation returns a nil

Alex Miller (Clojure team)16:10:53

you can't put nil on channels

Benjamin16:10:38

I see that explains it https://clojuredocs.org/clojure.core.async/pipeline-blocking this example needs to be fixed

Alex Miller (Clojure team)16:10:16

I think you can do so on clojuredocs if you log in

Benjamin16:10:02

damn son I logged in and fixed it! that was a magic moment for me

🎉 1
Alex Miller (Clojure team)16:10:52

this is community documentation

👍 1
sheluchin19:10:03

I need to loop over a seq, generate some new values for each item, create some side-effects, and then return the set of generated values. What's the best way to do that?

hiredman19:10:12

reduce is generally great

sheluchin19:10:50

And it's non-lazy so side-effects are okay there, yeah?

sheluchin19:10:36

Ok, makes sense... as long as there isn't a more appropriate abstraction.

phronmophobic19:10:41

or potentially, transduce if you can break up your computation into steps that look like map, filter, partition-all, cat, etc.

sheluchin19:10:48

Thanks. I haven't gotten into transducers yet but I'll check that out.

slipset19:10:19

It might be worth seeing if you’re able to separate the sideeffecting stuff from the pure stuff.

slipset19:10:53

As in, rather than having one big reduce which does a bunch of calculations and some sideeffects, try to split it up in a pipeline like:

(->> some-data (map interesting-transform) (filter garbage?) (reduce persist! some-initial-value))

slipset19:10:20

(of course, map + filter is nicely performed by keep )

👀 1
sheluchin19:10:36

Haven't heard of keep.

slipset19:10:00

if you’re not using the return value from your sideffecting stuff, you could also consider using a doseq or run!to perform the sideffecting stuff.

ghadi19:10:18

but your instincts to avoid transducers while getting started are on point:ok_hand:

ghadi19:10:35

lots of other fundamentals to learn

sheluchin19:10:39

Clojure's lexicon is just amazing.

sheluchin19:10:16

Basically in this case I'm starting with a set of values, combining those values into a new set of maps with some computed values, and storing each map in my backend db. I suppose I could separate the map generation from the saving thereof.

slipset19:10:38

I would strongly suggest that.

slipset19:10:50

And while doing that, have a look at this post about redundant fns when using map et al. https://stuartsierra.com/2015/08/10/clojure-donts-redundant-map

sheluchin19:10:38

Those Clojure Do's and Don'ts articles are a goldmine. Thanks for the link, haven't read that particular one.

sheluchin19:10:30

That's pretty straight-forward advice there. I think at this point I understand many of those idioms, but lack the familiarity of choosing the right abstraction function when dealings with collections. Map, reduce, filter are the general tools that can do just about everything, but I'm finding I often come across abstraction functions that cover the stuff I implement myself with those basic fns.

sheluchin20:10:23

I went with a let instead of threading it. Any pointers for improving this, or does it seem okay?

(let [lines (-> files first :tempfile slurp (string/split #"\n"))        
      notes (map (fn [line] {:note/id (UUID/randomUUID)                  
                             :note/text line                             
                             :note/note-list {:note-list/id :singleton}})
                 lines)                                                  
      ids (mapv #(select-keys % [:note/id]) notes)                       
      res {:note-list/notes ids}]                                        
  (doseq [note notes]                                                    
    (m/add-note! env note))                                              
  res))                                                                  

Colben23:10:22

Hi, I am trying to use built-in leiningen in IntelliJ Cursive, but I am not successful and need to use leiningen from CLI. Is the leiningen which is built-in into Cursive fully usable? For example I didn't find a way how to execute lein cljsbuild once cljsbuild is not in the list of tasks in the leinigen window and I don't see any way to add it.