Fork me on GitHub
#beginners
<
2023-02-17
>
Uli10:02:20

Hello. What is the idiomatic way to use configs in my clojure code. Essentially the value only is needed in one module (so no global state). But how do I provide it there? Using an atom?

Uli10:02:16

the value is only loaded once at startup of the app

nikolavojicic10:02:38

You can just use ordinary Clojure map and pass it as an argument where needed. Or, if you want to separate config from code, you can put that map inside .edn file and then load it:

(require '[clojure.edn :as edn])

(edn/read-string (slurp "config.edn"))
You need atom only if something changes over time. For larger applications you can use DI libraries such as https://github.com/weavejester/integrant https://github.com/stuartsierra/component https://github.com/tolitius/mount

👍 2
Uli10:02:31

but i can only read the config in -main function and inject somewhere. In my case I have a compojure route, where every call to a route will need to follow a config-settig. So how can i inject that config in a function, that is being called upon a rest-call

Uli11:02:25

ok, now that I write it and think again about it, I think I found an approach, thanks for chatting with me 😄

Timofey Sitnikov13:02:49

Good Morning! I stepped away from learning Clojure for almost a year and now I am back. I was looking for an answer and I asked ChatGPT, here is the text:

? │ find first element in array that meets criteria
 A │ You can use the clojure.core/find function to find the first element in an array that meets a boolean criteria.
   │ Example:
   │ (clojure.core/find #(> % 5) [1 2 3 4 5 6 7 8 9 10])
   │ This will return the first element in the array that is greater than 5, which is 6.
But, the find does not work like that, or am I missing something?

lispyclouds13:02:25

yeah that is wrong. chatgpt isnt guaranteed to be correct 😛

Timofey Sitnikov14:02:45

Wow, OK, I just wanted to have someone who knows more than me confirm. Thank you...

lispyclouds14:02:42

there's only so much that can be fact checked, take things it says with a grain of salt, not as absolute source of truth 😄

sb14:02:01

I got this from chatgpt ‘(defn find-first-greater-than-5 [vector] (first (filter #(> % 5) vector)))’

Timofey Sitnikov14:02:53

@U2T2ZEVPC, would that be the best way of doing it?

Timofey Sitnikov14:02:39

I mean is the ChatGPT recommendation the best?

sb14:02:47

Interesting.. still not so good

Timofey Sitnikov14:02:57

How would you do it?

sb14:02:38

Sorry, That was correct. I wrote down >5 😅

lispyclouds14:02:39

(some #(when (> % 5) %) [1 2 3 4 5 6 7 8 9 10]) should be better i think

lispyclouds14:02:55

stops at the first match, unlike filter

👍 4
2
Timofey Sitnikov14:02:47

That is interesting ...

Timofey Sitnikov14:02:05

Would have never though of using these 2 functions.

Mario Trost16:02:15

Does it matter much performance wise if I use filter or the some solution? Because filter returns a lazy sequence?

rolt16:02:29

also filter + first will realise the first 32 elements

2
lispyclouds16:02:32

And if you want to not create an unnecessary seq just to drop it after the first call, some is better. I’d use filter if I’m using the seq in other places too, not just for the first call.

2
stantheman16:02:34

And looking at the source for some and filter, some is more more succinct...

pavlosmelissinos17:02:34

filter & first might be technically less efficient but some & when is far less intuitive and this is #beginners. In other words, filter & first is a perfectly fine answer to this question (I'd only maybe suggest some & when if this question popped up in #clojure)

jumar05:02:30

Not in Clojure core but I use medley.core/find-first

slk50014:02:42

I don't understand this behaviour. Trying to get data from api json ajax/GET. It works when it's just array of objects: [{"name":"mario"},{"name":"luigi"}] But it don't work when this array of objects is under data key. {"data":[{"name":"mario"},{"name":"luigi"} Error: Uncaught Error: nth not supported on this type cljs.core/PersistentArrayMap. As I understand it converts this json to clojurescript code and try run 'data' as function. example code: (defn get-tag-list [] (let [handler (fn [[ok response]] (if ok (js/console.log "ok") (js/console.log "error")))] (ajax/GET "" {:handler handler}))) So how to unpack data from 'data' key?

Bob B14:02:19

assuming cljs-ajax, handler just gets the response, and the fn's arglist is destructuring, so in the case when a response is sequential (e.g. a vector of maps), ok will be bound to first item and response will be bound to the second. In the case where response is a map, the destructuring fails because maps aren't sequential (which is why nth isn't supported on them, and nth is used to destructure)

👍 2
Bob B15:02:33

...so you could destructure using map destructuring, or not destructure at all and do whatever you need to do with the response, maybe checking whether it's a list or a map if your call might result in either

👍 2
kennytilton15:02:10

@UPBB20W20 It is not clear from your example code what is not working. Like, what is "it" in "it works" and "it doesn't work"? That said, I will hazard a guess based on the question: We do have to adjust our response parsing to whatever the source cares to send along. So (still guessing), where you have (first response), try (first (:data response)) -- to handle this source. Hth!

👍 2
slk50018:02:59

thank you guys that was silly mistake :/ I'm still confused with handler function. Like passing 'atom app-state' to handler function. (defonce app-state (atom {:tags [] :music-videos []})) (get-tag-list app-state) (defn get-tag-list [app-state] (ajax/GET "" {:handler #(swap! app-state assoc :tags (:data %)) :response-format :json :keywords? true})) In PHP I would just return data from 'get-tag-list' a then save it somewhere. In my next step I need to make a <ul> <li> list from result of this api call. Do I have to create another atom and pass it to :handler? Can I just make a html list right away?

mathpunk18:02:39

Time is hard. I want to partition-by some files by what date they were modified on. I got as far as

(require '[java-time.api :as time])

(let [reports (file-seq (io/as-file path-to-reports))]
  (time/instant (.lastModified (first reports))))
but I cannot figure out how to turn an instant into just a date.

phronmophobic18:02:55

The java.time is pretty functional on its own. I've never really felt the need to wrap it (not that thers's anything wrong with wrapping).

mathpunk18:02:45

see I thought so but

(time/local-date (time/instant (.lastModified (first reports))))
yields
Execution error (ExceptionInfo) at java-time.defconversion/call-conversion (defconversion.clj:69). Could not convert [#inst "2023-02-17T17:32:33.509000000-00:00"] to class java.time.LocalDate!

phronmophobic18:02:37

If you look at the second link, converting from an instant to a localdate requires a ZoneId

mathpunk18:02:16

getting closer

(let [reports (file-seq (io/as-file path-to-reports))]
   (.ofInstant (time/instant (.lastModified (first reports))) "CST"))
but that’s not how to hold a zone ID

phronmophobic18:02:16

you probably want (java.time.ZoneId/systemDefault)

phronmophobic18:02:38

(java.time.LocalDate/ofInstant
 (java.time.Instant/now)
 (java.time.ZoneId/systemDefault))

phronmophobic18:02:11

ofInstant is a static method of LocalDate

mathpunk18:02:29

(let [reports (file-seq (io/as-file path-to-reports))
        zone-id (java.time.ZoneId/systemDefault)]
   (.ofInstant (time/instant (.lastModified (first reports))) zone-id))
yields
No matching method ofInstant found taking 1 args for class java.time.Instant 

mathpunk18:02:49

which would make me think my parentheses are wrong but I really don’t think they are hmmmm

mathpunk18:02:13

oh, now I see

mathpunk18:02:17

I don’t think I understand how to static method

mathpunk18:02:27

it’s not time/LocalDate/ofInstant

phronmophobic18:02:47

no worries, java interop can be a bit tricky at first

phronmophobic18:02:13

it's java.time.LocalDate/ofInstant or LocalDate/ofInstant if you've imported LocalDate

mathpunk18:02:17

so this

(java.time.LocalDate/ofInstant (time/instant (.lastModified (first reports))) zone-id)
does it, but,

mathpunk18:02:26

when you say “imported”

mathpunk18:02:35

you mean something different than require

phronmophobic18:02:02

yes, there's an optional :import allowed in your ns statement

mathpunk18:02:04

I was expecting to be able to replace java.time with the time alias in my require

phronmophobic18:02:42

(ns my.ns
  (:require ...)
  (:import java.time.LocalDate))

mathpunk18:02:31

so do I have this right: I was trying to use functions defined in the namespace java.time. But to use the static method of the java.time.LocalDate class (class?), it must be imported

👍 2
mathpunk18:02:57

(or fully qualified I spose)

👍 2
mathpunk18:02:33

at any rate, that’s my problem solved, thanks so much

mathpunk19:02:22

One more bit of java interop confusion: This function is fine

(defn creation-date [report]
  (LocalDate/ofInstant (time/instant (.lastModified report)) (ZoneId/systemDefault)))
This function is not
(defn creation-date [report]
  (-> report .lastModified time/instant #(LocalDate/ofInstant % (ZoneId/systemDefault))))
They’re evidently not equivalent but I’m not sure why

mathpunk19:02:04

I bet it’s because -> is a macro

mathpunk19:02:10

but that’s where my guess stops

phronmophobic19:02:11

I think the # is unnecessary

mathpunk19:02:04

ah ha, that’s it — I forgot that what’s being passed will be the first argument

mathpunk19:02:30

not that the threading version is that much more readable, but I was wondering if it was interop

phronmophobic19:02:33

generally, anonymous functions inside of threading macros usually don't do what you want

📝 2
2
phronmophobic19:02:23

> (read-string "#()")
(fn* [] ())
anonymous functions get expanded before macros, so you get weird results
> (clojure.walk/macroexpand-all '(-> foo #()))
(fn* foo [] ())

💯 2