Fork me on GitHub
#beginners
<
2020-04-24
>
tjb00:04:48

im having trouble trying to articulate in clojure how to code the following function. given a list of keywords, i want to iterate over that list and check if a map contains that keyword. if it does i want to do an action (in my case assoc it with a new value). can anyone give me a direct to look in? i have tried map and for and keep hitting a wall

andy.fingerhut00:04:33

If you know the key/value pairs you want to merge in ahead of time and always add them with that value regardless of whether they are already in the original map, or not, then merge can do that. Since you say you want the new values to be used only if the key is already present in the original map, that isn't merge. reduce is a pretty general looping construct, something like (reduce original-map (fn [cur-map k] (if (contains? cur-map k) (assoc cur-map k new-val) cur-map) list-of-keys) is the general kind of pattern that seems like it would work for you.

andy.fingerhut00:04:46

except I messed up the order of arguments to reduce there. original-map should be just before list-of-keys

noisesmith00:04:52

+1 for the reduce approach

tjb01:04:35

andy this is brilliant!!!

tjb01:04:36

thank you!

tjb02:04:25

reshred.system.server=> 
(def mm {:status 200, :body nil})
#'reshred.system.server/mm
reshred.system.server=> 
(assoc mm :body body)
Syntax error compiling at (form-init8744011881109093941.clj:1:1).
Unable to resolve symbol: body in this context
class clojure.lang.Compiler$CompilerException
Show: AllClojureJavaToolingDuplicates
reshred.system.server=> 
(println mm)
{:status 200, :body nil}
nil

tjb02:04:44

why cant i update or assoc mm ? i get an error every time

andy.fingerhut02:04:18

The Unable to resolve symbol: body in this context is the part of that error message that stands out to me.

tjb02:04:24

reshred.system.server=> 
(update mm :body {:id 1})
{:status 200, :body nil}

tjb02:04:28

another example ^

tjb02:04:32

the :body does not update

andy.fingerhut02:04:18

Try (assoc mm :body {:id 1})

tjb02:04:48

hmmmmmmmm

tjb02:04:50

interesting

andy.fingerhut02:04:03

The error message I called out above is because the symbol body had no value defined for it at the time you tried to evaluate (assoc mm :body body)

tjb02:04:25

so if the keyword is nil it will always bomb?

tjb02:04:36

mistype i understand

tjb02:04:52

the body variable is empty and not define

andy.fingerhut02:04:36

The reason that (update mm :body {:id 1}) returned the same value as mm is different -- it is because update and assoc take different kinds of arguments -- update takes a function and optionally arguments for it, and calls that function on the original value associated with the key

andy.fingerhut02:04:37

A Clojure map data structure can behave like a function, though, which is why there was no error. A Clojure map behaves like a function from its keys, returning their corresponding values.

andy.fingerhut02:04:47

And sorry if I am inundating you with too much, but one more REPL session for you to think about, demonstrating that assoc is a pure function, and the map that is the value of mm is immutable, and not changed by the call to assoc :

👍 4
✔️ 4
andy.fingerhut02:04:50

user=> (def mm {:status 200, :body nil})
#'user/mm
user=> (assoc mm :body {:foo 1 :bar 2})
{:status 200, :body {:foo 1, :bar 2}}
user=> mm
{:status 200, :body nil}

andy.fingerhut02:04:25

assoc takes one map as input, and returns a new one, which usually shares memory and most of its key/value pairs with the input map.

Steiner02:04:49

hello,everyone. I'm trying to make Number of combinations with FP,but I have no idea.Any one can help me? just like this function (defn C [nums n] ...) means take n from nums into making Number of combinations

andy.fingerhut02:04:07

So you don't want a function like (num-combination 5 3) that returns 10. You want a function like (combinations ["a" "b" "c" "d" "e"] 3) that returns a collection of 10 3-element collections, all different?

Steiner02:04:24

yes,that's it

andy.fingerhut02:04:16

Do you already have an idea of how you might solve it, in words, perhaps using some kind of recursive approach?

Steiner02:04:35

sorry,not yet

Steiner02:04:05

but i have thought it may be recur

andy.fingerhut02:04:42

There are definitely multiple ways to solve this, and if this is part of an exercise to learn how to solve such problems, I wouldn't want to just give the whole answer away all at once 🙂

andy.fingerhut02:04:32

Clojure's recur can be used for some kinds of recursive approaches, but not all of them. I suspect it would be simpler to ignore the existence of recur at first, and think of that as an optimization that might be useful later, after you have something working that doesn't use recur

andy.fingerhut02:04:38

I can give a significant hint for one way to solve it recursively, first just in words rather than writing code for it.

Steiner02:04:03

I don't mind that

andy.fingerhut02:04:41

If you think of all of the 3-element subsets of the 5-element set #{"a" "b" "c" "d" "e"} some of them contain the element "a", and some do not.

andy.fingerhut02:04:44

Of all of the 3-element sets that do contain "a", how many other elements besides "a" must they have in them?

Steiner02:04:00

sorry,I haven't used to communicate with English,please hang on

andy.fingerhut02:04:15

No problem. Take your time.

andy.fingerhut02:04:01

Do you mean like: given 5 people, how many different sets of 3 people can you select from those 5?

andy.fingerhut02:04:50

And you already know the formula from math for this, probably?

Steiner02:04:58

let's start a thread??

tjb02:04:31

@andy.fingerhut thank you for the insight!

tjb02:04:46

im listening to a POST request incoming and seem to be back to square one of my issue that i mentioned in https://clojurians.slack.com/archives/C053AK3F9/p1587612929306400. I am now able to transform the incoming body to a UUID type but i am getting the following error

java.lang.IllegalArgumentException: No implementation of method: :write-body-to-stream of protocol: #'ring.core.protocols/StreamableResponseBody found for class: clojure.lang.PersistentArrayMap
my code looks like this
(def ids-to-replace
  [:id :user_id :seller_id])

(defn ^:private id->uuid
  [original-map to-replace]
  (reduce
   (fn [cur-map k]
     (if (contains? cur-map k)
       (assoc cur-map k (string-util/to-uuid (k cur-map))) cur-map))
   original-map to-replace))

(defn wrap-uuid-type
  [handler]
  (fn
    ([request]
     (println "first on submit")
     (let [response (handler request)
           body (id->uuid (json/read-str (:body response) :key-fn keyword) ids-to-replace)]
       (println (assoc response :body body))
       (assoc response :body body)))))

(defn wrap-database-component [handler database]
  (fn [req]
    (handler (assoc req :database database))))

(defn routes-handler [database]
  (println "routers-handler fired")
  (-> routes/define-main-routes
      (wrap-uuid-type)
      (wrap-database-component database)))

dpsutton02:04:57

i'm just catching up. can you say in words what you want to achieve with wrap-uuid-type?

tjb03:04:32

sure! so i have an incoming json from a POST request that is like

{
  id: "uuid-here-but-as-a-string"
}
and i want to intercept this and change the type of the id to UUID so my database doesnt yell at me for it being the wrong type. i was hoping that i could do this via a middleware so i dont have to wrap each POST function with a converter

tjb03:04:36

i thought i had it last night but i am mistaken 😞

dpsutton03:04:05

ah ok. so you want to modify the request before the handler i think. see how you do response (handler request) ? that means the request is going in unchanged. you want to (handler (change-things-to-uuids request) instead

tjb03:04:53

if i dont call the handler early then the :body is an input stream

tjb03:04:29

i ahve it working in other places where i can do response (handler request) and just return response and things work ok

tjb03:04:04

my issue is after i do the replacement it says it cannot write it back to a stream which makes me think there is no good way to do this

dpsutton03:04:22

sure. but that means the handler you are calling is not getting an updated request

dpsutton03:04:29

also, look at this error message

dpsutton03:04:30

> No implementation of method: :write-body-to-stream of protocol: #'ring.core.protocols/StreamableResponseBody found for class: clojure.lang.PersistentArrayMap

dpsutton03:04:03

can't write a map to body-stream is how i read that

dpsutton03:04:33

look at these two handlers:

(defn handler [request]
  {:status 200
   :headers {"Content-Type" "application/edn"}
   :body {:data {:important :stuff}}})

(defn handler [request]
  {:status 200
   :headers {"Content-Type" "application/edn"}
   :body (pr-str {:data {:important :stuff}})})
the first handler throws the same error as you are seeing. the second one does not

tjb03:04:40

what does pr-str do?

tjb03:04:09

and yes per the error you are correct. i have to convert the :body to a keyword made and then do the replacement

tjb03:04:21

(id->uuid (json/read-str (:body response) :key-fn keyword) ids-to-replace)

tjb03:04:31

(defn ^:private id->uuid
  [original-map to-replace]
  (reduce
   (fn [cur-map k]
     (if (contains? cur-map k)
       (assoc cur-map k (string-util/to-uuid (k cur-map))) cur-map))
   original-map to-replace))

dpsutton03:04:34

(pr-str {:a :b}) => "{:a :b}"

✔️ 4
dpsutton03:04:07

and pr-str: > pr to a string, returning it

tjb03:04:09

im afraid ill get the same error as i did with str where it has no idea how to serialize the UUID type

tjb03:04:13

let me check

dpsutton03:04:24

clojure.core/pr
([] [x] [x & more])
  Prints the object(s) to the output stream that is the current value
  of *out*.  Prints the object(s), separated by spaces if there is
  more than one.  By default, pr and prn print in a way that objects
  can be read by the reader

‼️ 4
tjb03:04:29

oh it legit just prints it

tjb03:04:31

{:id #uuid "c0c03eff-9870-4c0e-83ac-506bf59d69cf", :first "Yusuke", :last "Urameshi", :email ""}

tjb03:04:39

is how the JSON response is returned to my client 😕

dpsutton03:04:45

emphasis here: > By default, pr and prn print in a way that objects > can be read by the reader

dpsutton03:04:51

JSON is a string

dpsutton03:04:12

so just emit json as the body

dpsutton03:04:58

that's not json

dpsutton03:04:03

that's edn basically

dpsutton03:04:19

so now your only issue is turning clojure datastructures into json strings rather than edn strings

tjb03:04:35

which i hope (wrap-json-body {:keywords? true}) would take care of

dpsutton03:04:40

you've seen the helpful pr-str and now you need to figure out how to turn clojure datastructures into json

tjb03:04:43

but it seems with the changes this is ignored

dpsutton03:04:00

where did you find that function? is that a lib or in ring core i forgot

tjb03:04:27

[ring.middleware.json :refer [wrap-json-response, wrap-json-body]]

dpsutton03:04:14

don't you want to wrap-json-response?

dpsutton03:04:19

you want json to go back to the client

dpsutton03:04:10

am i understanding correctly? you are happy with what you are receiving from your client but you are not happy with the format you are sending back to the client

tjb03:04:11

> Middleware that converts responses with a map or a vector for a body into a JSON response.

tjb03:04:22

isnt that what this is doing? it should convert the map into a JSON response

tjb03:04:29

let me try and detail the flow out a bit

dpsutton03:04:07

> wrap-json-response

tjb03:04:09

Request client -> middlware -> replace ID with UUID type -> pass to controller -> save in DB Response Save in db --> response middlware --> convert to JSON --> client

dpsutton03:04:23

> which i hope `(wrap-json-body {:keywords? true})` would take care of

dpsutton03:04:31

notice json-body versus json-response?

tjb03:04:01

yeah! after reading the desc i think i was confusing myself

tjb03:04:41

> Middleware that parses the body of JSON request maps, and replaces the :body key with the parsed data structure.

dpsutton03:04:43

ok. so i think i follow what you're trying to do but i'm not sure what parts are working and you're happy with and which parts you are still working on

tjb03:04:53

perhaps this is what i want instead of pr-str

dpsutton03:04:04

yes exactly

tjb03:04:14

ok let me try one second pls

tjb03:04:29

on my request middleware i have this now

tjb03:04:32

(defn routes-handler [database]
  (println "routers-handler fired")
  (-> routes/define-main-routes
      (wrap-json-body {:keywords? true})
      (wrap-uuid-type)
      (wrap-database-component database)))

tjb03:04:51

cause i was json-body to resolve prior to feeding into wrap-uuid-type

tjb03:04:58

i think i have that right :thinking_face:

tjb03:04:46

now if i print the request in wrap-uuid-type i would expect my :body to be a keyword map

tjb03:04:15

if i just print request i get

:body #object[org.eclipse.jetty.server.HttpInputOverHTTP 0x784ebb6c [email protected][c=0,q=0,[0]=null,s=STREAM]]
if i print (handler request) i get
:body {"id":"c0c03eff-9870-4c0e-83ac-506bf59d69cf","first":"Yusuke","last":"Urameshi","email":""}

dpsutton03:04:59

what happens if you swap the order of wrap-uuid-type and wrap-json-body?

tjb03:04:46

same types 😞

tjb03:04:05

trying to figure this out via the REPL too

dpsutton03:04:49

and you're sure that you saw the new updated code and not the old code?

tjb03:04:06

correct i restarted my server and all

dpsutton03:04:12

follow https://github.com/ring-clojure/ring-json#wrap-json-body and make sure you can verify that the stuff coming in is json

tjb03:04:29

ok checking now

tjb03:04:34

was trying some stuff but no dice haha

dpsutton03:04:56

can i recommend that we follow those docs and don't move on until we understand that part?

dpsutton03:04:10

i think we understand sending back json but lets work on getting json in

tjb03:04:14

yes in agreement w that

tjb03:04:21

ok it was broke af

tjb03:04:28

UNTIL i added a malformed message

tjb03:04:38

(wrap-json-body handler {:keywords? true :or {:message "Broke"}})

dpsutton03:04:38

i'm not following

tjb03:04:09

nvm its not important. when i tried to load my app nothing would load but with the :or message responses happen again

tjb03:04:28

when i try to submit JSON now from my client to the API server the :body is still in the same type

:body #object[org.eclipse.jetty.server.HttpInputOverHTTP 0x2fa5e7f4 "[email protected][c=0,q=0,[0]=null,s=STREAM]"]

tjb03:04:17

ok i made some progress

tjb03:04:04

instead of using my angular app i went to postman and it seems like the content-type is not getting set to application/json so it was being ignored

dpsutton03:04:06

ring.middleware.json/wrap-json-body
([handler] [handler options])
  Middleware that parses the body of JSON request maps, and replaces the :body
  key with the parsed data structure. Requests without a JSON content type are
  unaffected.

dpsutton03:04:50

cool. so are you getting json parsed as clojure data structures in the handler now?

tjb03:04:59

i am still not 😞

tjb03:04:03

(defn handler [request]
  (println "json-body handler")
  (println (handler request)))

(defn routes-handler [database]
  (println "routers-handler fired")
  (-> routes/define-main-routes
      (wrap-json-body handler {:keywords? true :or {:message "Broke"}})
      (wrap-uuid-type)
      (wrap-database-component database)))

dpsutton03:04:16

take handler out of there

dpsutton03:04:46

wrap-json-body needs to wrap your define-main-routes

dpsutton03:04:03

also i don't think that's valid clojure

tjb03:04:07

no request is printed 😕

tjb03:04:12

which is not valid?

dpsutton03:04:38

{:keywords? true :or {:message "Broke"}} oh i guess it is but that's super weird to me

dpsutton03:04:18

i think that should be {:keywords true :malformed-response some-json-here}

dpsutton03:04:17

(ns server
  (:require [ring.adapter.jetty :as jetty]
            [ring.middleware.json :refer [wrap-json-body wrap-json-response]]))

(defn handler [request]
  (prn (get request :body))
  {:status 200
   :headers {"Content-Type" "application/edn"}
   :body {:data {:important :stuff}}})

(defn run-server []
  (jetty/run-jetty (wrap-json-response (wrap-json-body #'handler))
                   {:port  3000
                    :join? false}))

dpsutton03:04:57

^ that should work

dpsutton03:04:47

and that's the same as

(defn run-server []
  (jetty/run-jetty (-> handler
                       wrap-json-body
                       wrap-json-response)
                   {:port  3000
                    :join? false}))

dpsutton03:04:56

the threading is just wrapping them

tjb03:04:59

correct i am just really confused by this (wrap-json-body) seems to be doing nothing

dpsutton03:04:52

you're calling it with too many arguments right? see i'm passing handler into mine. but you're calling it with both routes/define-main-routes and handler?

(-> routes/define-main-routes
      (wrap-json-body handler {:keywords? true :or {:message "Broke"}})

tjb03:04:14

yeah i removed the handler i got confused with a message above

dpsutton03:04:20

and we read the docstring that wrap-json-body will only do work if the application/json content type header is set

dpsutton03:04:31

can you post your main function here again?

tjb03:04:37

correct so i have the content type set

tjb03:04:43

(defn routes-handler [database]
  (-> routes/define-main-routes
      (wrap-json-body {:keywords? true :or {:message "Broke"}})
      (wrap-uuid-type)
      (wrap-database-component database)))

dpsutton03:04:20

ok. and which route in define-main-routes are you hitting?

tjb03:04:35

(defroutes define-main-routes
  (context "/api/v1/items" [] (wrap-routes item/define-routes))
  (context "/api/v1/users" [] (wrap-routes user/define-routes))
  (context "/api" [] (wrap-routes app-routes))
  (route/not-found "Incorrect API endpoint."))

dpsutton03:04:35

and presumably go put a (prn request) in that handler

dpsutton03:04:03

which route do you end up? those are kinda more routes

tjb03:04:19

which one do i end up hitting?

dpsutton03:04:33

you tell me

dpsutton03:04:36

you're sending a request

dpsutton03:04:41

which handler should end up handling it

tjb03:04:43

yeah sorry im clarifying that is what you are asking

tjb03:04:03

ok word so i am drilling into "api/v1/users"

tjb03:04:10

which points to this method

tjb03:04:12

(def define-routes
  "Return all user routes"
  (routes
   (GET "/:id" [id :as req] (response (get-user id req)))
   (GET "/:id/address" [id :as req] (response (get-address-by-user id req)))
   (POST "/" req (response (create req)))
   (POST "/:id" [id :as req] (response (update-user id req)))))

tjb03:04:22

and i am hitting this function

tjb03:04:37

(POST "/:id" [id :as req] (response (update-user id req)))

dpsutton04:04:26

replace it with (POST "/:id" [id :as req] (do (prn req) (response (update-user id req))))

tjb04:04:57

stand by

tjb04:04:07

the body prints correctly as expected

:body {:id "c0c03eff-9870-4c0e-83ac-506bf59d69cf", :first "Yusuke", :last "Urameshi", :email ""}

dpsutton04:04:22

then we are turning json into clojure

dpsutton04:04:27

and wrap-json-body is working

tjb04:04:34

BUT i want to intercept this request prior to getting to this point is what im attempting to do

tjb04:04:45

does that makes sense?

dpsutton04:04:48

then lets try that now that we know the json is working

tjb04:04:52

or is that sorta a no-go with clojure?

dpsutton04:04:52

which i didn't think we had seen before

dpsutton04:04:55

we've made progress

dpsutton04:04:03

that totally makes sense

dpsutton04:04:17

we're gonna update that request. that's exactly what middleware do

tjb04:04:30

that is my understanding too 🙂

dpsutton04:04:08

and its gonna be basically this

dpsutton04:04:10

(defn with-uuids
  [handler]
  (fn [request]
    (handler (update-in request [:body :uuid-key] #(java.util.UUID/fromString %)))))

tjb04:04:41

where would this live?

dpsutton04:04:01

so that's a middleware functin

dpsutton04:04:20

put it in the ns next to where your app handler is defined. the one that hooks up all the middleware

tjb04:04:23

maybe ive been looking at the wrong spot cause i thought it would go in this thread

tjb04:04:25

(defn routes-handler [database]
  (-> routes/define-main-routes
      (wrap-json-body {:keywords? true :or {:message "Broke"}})
      (wrap-uuid-type)
      (wrap-database-component database)))

dpsutton04:04:43

right put it in there

dpsutton04:04:47

yes it will go in that thread

dpsutton04:04:54

now before or after the line with wrap json body

tjb04:04:19

yeah that is what wrap-uuid-type does but we are back to the :body being an input stream

dpsutton04:04:44

ok. so we have our middleware out of order then

dpsutton04:04:53

put the middleware on the other side of wrap-json-body then

dpsutton04:04:00

if its below put it above; if above put below

tjb04:04:28

right this is what happens in both situations

dpsutton04:04:52

use my function not yours

dpsutton04:04:57

or post yours

dpsutton04:04:00

its not correct

tjb04:04:10

let me try yours first before we go to mine

tjb04:04:14

ill comment mine out

dpsutton04:04:24

yeah we need this to run after the body has been turned into json

👍 4
dpsutton04:04:37

and we will update the uuid to be a UUID type and then call the handler on that

tjb04:04:47

im so effing confused why it is working now

tjb04:04:53

maybe my function was wrong?

dpsutton04:04:58

yeah it was super wrong

dpsutton04:04:01

lets look over it

tjb04:04:13

sure so i had this

tjb04:04:16

(defn wrap-uuid-type
  [handler]
  (fn
    ([request]
     (let [body (id->uuid (:body request) ids-to-replace)]
       (handler (assoc request :body body))))))

tjb04:04:22

and id->uuid is this

tjb04:04:30

(defn ^:private id->uuid
  [original-map to-replace]
  (reduce
   (fn [cur-map k]
     (if (contains? cur-map k)
       (assoc cur-map k (string-util/to-uuid (k cur-map))) cur-map))
   original-map to-replace))

tjb04:04:46

let me post all three it will be more readable sorry

tjb04:04:49

(def ids-to-replace
  [:id :user_id :seller_id])

(defn ^:private id->uuid
  [original-map to-replace]
  (reduce
   (fn [cur-map k]
     (if (contains? cur-map k)
       (assoc cur-map k (string-util/to-uuid (k cur-map))) cur-map))
   original-map to-replace))

(defn wrap-uuid-type
  [handler]
  (fn
    ([request]
     (let [body (id->uuid (:body request) ids-to-replace)]
       (handler (assoc request :body body))))))

tjb04:04:25

now i tried ^ and it works?

tjb04:04:30

im so confused what has changed....

dpsutton04:04:29

that's different from earlier

dpsutton04:04:31

(defn wrap-uuid-type
  [handler]
  (fn
    ([request]
     (println "first on submit")
     (let [response (handler request)
           body (id->uuid (json/read-str (:body response) :key-fn keyword) ids-to-replace)]
       (println (assoc response :body body))
       (assoc response :body body)))))

tjb04:04:04

yeah im confusing myself and changing things then getting frustrated and forgetting what i did

dpsutton04:04:06

so couple things wrong. imagine this is run before the body is changed into a clojure map

dpsutton04:04:26

scratch that. you see (let [response (handler request) this line? You're calling the handler on the request without updating the body to be either json or uuid'd

dpsutton04:04:31

so double wrong

tjb04:04:49

so posting now works like a charm which is nice but i see where i was confusing myself

dpsutton04:04:53

now we've separated things. we have just a json middleware that turns application/json requests into clojure maps

tjb04:04:31

now when we fetch data we are getting the stream issue

dpsutton04:04:46

what's the stream issue?

tjb04:04:51

which the error looks like this

tjb04:04:54

java.lang.IllegalArgumentException: contains? not supported on type: org.eclipse.jetty.server.HttpInputOverHTTP

dpsutton04:04:26

ok. so you're calling something on a map but you haven't run the json middleware

4
tjb04:04:30

since the body looks like

:body #object[org.eclipse.jetty.server.HttpInputOverHTTP 0x1ab8640f "[email protected][c=0,q=0,[0]=null,s=STREAM]"]

dpsutton04:04:20

yup. so the json middleware didn't run?

tjb04:04:29

it seems like it, correct

dpsutton04:04:41

ok. why not? did the request include the application/json header?

‼️ 4
dpsutton04:04:14

and if so, did the json middleware run before this error happened?

tjb04:04:34

that may be it...let me see

tjb04:04:58

so i know for a fact i have not been explicitly setting it anywhere

tjb04:04:07

so that is probably the problem...

tjb04:04:19

in clojure land, should it be explicit in each endpoint?

dpsutton04:04:25

setting what?

tjb04:04:30

content type

dpsutton04:04:44

that's not set by an endpoint, that's set on a request

dpsutton04:04:09

yes every request needs to set its content-type to indicate that its sending json

tjb04:04:20

via chrome i see

Content-Type: application/json

tjb04:04:42

let me try with postman

dpsutton04:04:07

are you saying the request has the content type header correctly set?

tjb04:04:35

chrome says yes but i just tried in postman with explicitly setting the content type and it works

tjb04:04:42

so i think the angular app needs to explicitly set it

tjb04:04:44

one second

tjb04:04:54

no dice with explicitly setting it

tjb04:04:06

but works in postman so hmm

tjb04:04:24

this is def an angular issue now

tjb04:04:38

Dan thank you so much for being patient with me and helping me understand

tjb04:04:45

i truly appreciate the mentorship

dpsutton04:04:59

well hate to say it but awesome lol

😄 4
dpsutton04:04:13

with simple curls it should work

dpsutton04:04:26

curl localhost:3000 --request POST --header "Content-Type: application/json" --data '{"thing":"0f248583-d687-4417-b621-1ac1af92962a"}'

dpsutton04:04:30

that's what i was doing

tjb04:04:33

yup it does and i see that the content type is empty so its def a client facing issue now

dpsutton04:04:18

well at least we're square on the backend

dpsutton04:04:26

i hope the frontend can easily be set somewhere

dpsutton04:04:55

oh and i meant to say that you're incredibly welcome

dpsutton04:04:10

i hope your project goes smoothly in the future 🙂

tjb04:04:32

thank you again dan. huge s/o you spent over an hour with me today thanks a ton!! i hope it goes smoothly too 🙂

tjb02:04:27

i am not sure i am doing this tranformation at the right place or do i have to transform that request on the actual POST route for each POST request

sroller04:04:39

I'm trying to write the result of a lazy sequence in to a file. I tried

sroller04:04:12

(spit "/temp/logbook.edn" (doall (xml-seq logbook)))

sroller04:04:48

does anybody have an idea how to get around this?

seancorfield04:04:38

spit just calls str on the thing you pass in. doall realizes it all, but it's still a LazySeq object.

seancorfield04:04:57

What format do you want the data written out as?

alexmiller04:04:21

pr-str I think will help

seancorfield04:04:44

Yeah, I was about to suggest that but wondered what @sroller expected in the file...

alexmiller04:04:08

I presume a list

seancorfield04:04:10

@tjb He's a rock star! /cc @dpsutton

👍 20
🙏 8
🍻 8
mario-star 4
sroller05:04:34

thank you guys. I was eventually successful with (spit "file.edn" (with-out-str (clojure.pprint/pprint xml)))

sroller05:04:39

I'll try the pr-str method.

sroller05:04:55

my underlying problem: I'm trying to import 700mb xml from a my discontinued sporttrack3. It contains all my activities + GPS data since 2004. I'm playing around with Clojure since 2015. I'm finally getting serious with this one. (Did I mentioned I hate XML?)

sroller05:04:26

My approach is to suck the entire file in and then work over the structure created with xml/parse. I found zip/xml-zip but I doesn't yield the results I'm expecting. I'm still at the very beginning with this.

sroller05:04:11

ok - so pr-str obviously worked. I like the output of pprint better though. I can find better my way around. Need to sleep now, it's 1:17am. Cu l8ter.

sroller05:04:20

thanks again.

Daniel Stephens10:04:03

Is there something like test categories in clojure through meta data or something or do people just use different namespaces?

andy.fingerhut10:04:34

Leiningen offers the ability to mark deftest forms with keywords, and then select subsets of deftest forms whose tests should be run via a function given that keyword, and returns true or false, specified in the project.clj file.

andy.fingerhut10:04:14

By namespaces is another way. I would guess there are others used by at least some people, but don't have any kind of exhaustive list to provide.

Daniel Stephens10:04:55

Thanks Andy, do you know where that first one is documented, using a keyword? That's what I was looking for but I couldn't track it down

kelveden10:04:20

lein help test is a good start.

👍 4
Daniel Stephens10:04:48

ahh perfect! cheers

👍 4
Chris K11:04:17

Quick question about clojure equality check I am using emacs and I have flycheck-clj-kondo enabled and it gave me this message which I didn't understand

(= "X" X-find)

Chris K11:04:35

Single operand use of clojure.core/= is always true

Chris K11:04:06

wait nevermind sry guys, I forgot to update the file.... 😞

Aron11:04:42

what is an idiomatic way to build a hashmap from two other hashmap and a list of keys such ad that for each key there is an order (not always the same) from which of the two other hashmaps should be tried first, then the next, then nil for value

Aron11:04:09

I can type it out but I get the feeling this is exactly something I should be able to do more easily with clojure

andy.fingerhut11:04:48

merge is more restricted than that, in that it always considers all keys in the order that you give them, with the last map having a particular key having its value appear in the result.

borkdude11:04:18

@ashnur I think concrete input/output examples would be helpful here

☝️ 4
andy.fingerhut11:04:46

In the behavior you wish to have, do you want to be able to say that for key :foo, the order of precedence is "1st map, then 2nd map, then 3rd map", but for key :bar you want "2nd map, then 1st map, then 3rd map" ?

andy.fingerhut11:04:42

Hmmm. Never mind the "3rd map" part of my question, since you asked about only 2 input maps.

Aron11:04:17

I don't have the output encoded, it wouldn't be necessary to do the work if I had, but I can type out the logic

Aron12:04:29

input: [:key1, :key2, :key3] and assuming there are to hashmaps map1 map2 output would be {:key1 (or (get map1 key1) (get map2 key1)), :key2 (get map2 key2) :key3 (or (get map2 key3) (get map1 key3)) note how the order for keys can change or sometimes just one of the maps are used. But there are only two maps and the keys are the same

Aron12:04:13

because there are dozens of keys, even if it's easy to type it out, it's really hard to read and I would say it's not clean code

Aron12:04:04

basically, I am preparing a hashmap to be sent for a remote API and I need to use user input (map1) or app-state (map2)

andy.fingerhut12:04:18

So do you expect there to be another input, which for each key somehow describes whether map1 should take precedence, or map2 should?

andy.fingerhut12:04:11

For example, the input could contain two maps, and two collections of keys ks1 and ks2, where keys in ks1 prefer to get their value from map1, and keys in ks2 prefer to get their value from map2?

Aron12:04:36

yes, something needs to say in what order should the maps be tried

Aron12:04:53

I can even suspect that a third map might not be unlikely in some cases, although this is not the case at the moment

Aron12:04:22

so that's why I didn't want to use something like what you suggest @andy.fingerhut because if it turns out that there will be 3 maps to be read from, this breaks down

andy.fingerhut12:04:07

I can't suggest how to write a function to do this, unless I know what the inputs are. If you want to extend it to a 3rd map, then I'm even more in the dark what form you want to specify the input in.

Aron12:04:01

@andy.fingerhut I don't understand, my question I believe is exactly about the inputs. At the moment I just know what it needs to be there, but apart from 2 (or more) maps from which the keys have to come, and the actual keys, there is no input

Aron12:04:36

so the input you asking me for is exactly the same thing I am asking about: how to encode the logic of which order the maps should be read?

andy.fingerhut12:04:05

So you want to call the function with parameters like (frobnifier [:key1 :key2 :key3] map1 map2), and it somehow magically figures out that :key1 should prefer the value from map1?

andy.fingerhut12:04:31

I'd like to figure out how to avoid using magic.

Aron12:04:28

absolutely what I am aiming at 🙂 I think we agree about this

Aron12:04:05

but there are dozens of keys and for each key there might be different order, I need to encode this order somehow, I want to make it explicit and easy to read and easy to extend

Aron12:04:18

not simple necessarily 🙂

andy.fingerhut12:04:53

So here is a different function than the one I hint at above, I'll call it prefer-keys. It takes map1 and a collection ks1 whose values should be taken from map1 if they exist there, otherwise from map2. It also takes map2 and a collection ks2 of keys whose value should preferentially be taken from map2. Assumptions I will make for the moment, but you might prefer different behavior: (a) no key appears in both ks1 and ks2, and (b) the output map contains only keys in ks1 or ks2. If a key is in an input map that is in neither ks1 nor ks2, it will not be in the returned map.

andy.fingerhut12:04:20

(defn prefer-keys [m1 ks1 m2 ks2] (merge (select-keys m2 ks1) (select-keys m1 ks1) (select-keys m1 ks2) (select-keys m2 ks2))) user=> (prefer-keys {:a 1 :b 2} [:a :e] {:a -1 :c -3 :d -4} [:b :c]) {:a 1, 😛 2, :c -3}

👍 4
Aron12:04:32

the assumption sound correct to me

Aron12:04:06

hah, select-keys! thanks

Chris K13:04:32

I am looking for some ways I can debug when the program breaks at one point. So I am using emacs cider for clojure and then I have a program that will give me an error. It doesn't give me where it breaks or anything so I wanted to use a debugger to identify where my program breaks. So on emacs, I have to run the function (cider-eval-defun-at-point), but when I run that function, it gives me the error of the program and just doesn't work. Any tips or fixes?

anonimitoraf13:04:58

let me clarify, you don't get a stack trace?

Chris K13:04:44

wait..so I just got my eval defun at point to work, but I don't see how this is a debugger

Chris K14:04:11

like how would I actually use it? I thought I could go through each expressions and see where it breaks???

anonimitoraf14:04:45

For doom-emacs what I have to do is: • f9 on the function definition • f9 on the function call

anonimitoraf14:04:01

where f9 is cider-debug-defun-at-point

Chris K14:04:49

ohhh yeah ot

Chris K14:04:15

it's working now. I messed up the functions I was doing eval not the debug this whole time thxs a lot 😄

😄 4
NoahTheDuke15:04:35

is it possible to test a macro that throws an error if incorrectly written? if it was a normal function, I could write (is (thrown? IllegalArgumentException. TEST)), but when it's a macro, the error is thrown at expansion/compile

noisesmith15:04:20

you can use eval inside is

noisesmith15:04:35

or macroexpand

noisesmith15:04:58

(cmd)user=> (deftest foo-test (is (thrown? clojure.lang.ArityException (macroexpand '(when)))))
#'user/foo-test
(ins)user=> (foo-test)
nil

NoahTheDuke18:04:44

This is interesting. I've been playing with this, trying to get it to work, and the only way to "pass the test" is to say (thrown? clojure.lang.Compiler$CompilerException (macroexpand '(...)))

noisesmith18:04:03

you could also try/catch on the Compiler$CompilerException and then assert about the cause

NoahTheDuke18:04:28

okay, i'll give that a whirl

noisesmith18:04:53

hmm - but to really ensure the test runs and can fail, you should return the exception from the try/catch, then assert on that outside the catch

NoahTheDuke18:04:12

got that working. further testing has shown that using macroexpand in the repl works, but not in a test file run with lein test. Using eval works with lein test as long as I fully-qualify the name: cond-plus.core/cond+

NoahTheDuke18:04:16

thanks so much for the help

noisesmith18:04:07

macros are happy to expand to functions / macros that are not (yet?) in scope, so ensure that all the code that is in the expansion is required in the context you are using it (clojure won't enforce this in the way it would for functions)

noisesmith18:04:58

of course this turns into an error at runtime, it's just that it won't be reported in the way we are used to

noisesmith15:04:07

and the failing version

(ins)user=> (deftest foo-test (is (thrown? clojure.lang.ArityException (macroexpand '(when true)))))
#'user/foo-test
(cmd)user=> (foo-test)

FAIL in (foo-test) (NO_SOURCE_FILE:1)
expected: (thrown? clojure.lang.ArityException (macroexpand (quote (when true))))
  actual: nil
nil

Jose Ferreira15:04:33

I'm having a bit of trouble understanding an error comming from this validation code:

(defn validate-message
  ""
  [params]
  (first (st/validate params message-schema)))

Jose Ferreira15:04:52

class clojure.lang.Keyword cannot be cast to class clojure.lang.Associative (clojure.lang.Keyword and clojure.lang.Associative are in unnamed module of loader 'app')

noisesmith15:04:14

usually that means someone called (assoc x k v) and x was a keyword

noisesmith15:04:33

I'd check the args to validate, perhaps one of those args needs to be a map - the full stack trace (check *e ) will help here

👍 4
Jose Ferreira15:04:03

#error {
 :cause "Unable to resolve symbol: message-schema in this context"
 :via
 [{:type clojure.lang.Compiler$CompilerException
   :message "Syntax error compiling at (/home/jferreira/Develop/projects/clojure/gestbook/src/clj/gestbook/routes/home.clj:24:10)."

Jose Ferreira15:04:16

ok this is nice...didn't know about this stack trace

Jose Ferreira15:04:47

(def message-schema
  [[:name
    st/required
    st/string]
   [:message
    st/required
    st/string]
   {:message "message must contain at least 10 chars"
    :validate (fn [msg] (>= (count msg) 10))}])

Jose Ferreira15:04:00

So the problem is with this def...

Jose Ferreira15:04:27

why wouldn't the evaluator be able to check this symb?

noisesmith15:04:03

what happens when you load that on its own? it could be the definition just isn't in the right order (before usage) in the file?

Jose Ferreira15:04:50

When i eval it from the REPL there is no problem

noisesmith15:04:06

is the definition before the usage in the file?

Jose Ferreira15:04:16

yes, right above it

Jose Ferreira15:04:35

(def message-schema
  [[:name
    st/required
    st/string]
   [:message
    st/required
    st/string]
   {:message "message must contain at least 10 chars"
    :validate (fn [msg] (>= count msg 10))}])

(defn validate-message
  ""
  [params]
  (first (st/validate params message-schema)))

(defn home-page [{:keys [flash] :as request }]
  (layout/render request "home.html" (merge {:messages (db/get-messages)}
                                             (select-keys flash [:name :message :errors]))))

Jose Ferreira15:04:38

calling it directly from the REPL says theres a syntax error....

Jose Ferreira15:04:56

oh, i think i see it, i'm closing the message vector before the map

Jose Ferreira16:04:46

It's working now

NoahTheDuke16:04:08

thanks @noisesmith! very helpful

nick17:04:56

How can I prepend all keys in a given map with "foo"? (def raw {:id 71 :name "whatever"}) ;; expected => {:foo/id 71, :foo/name "whatever"} I would like it to be generic so I can't use #rename-keys with a predefined list of from=>to rules. The bigger task is the transformation of raw jdbc result to the proper Pathom format.

dpsutton17:04:52

your example makes the key have a namespace "bar" but your sentence says you want to prepend "foo"

nick17:04:54

sorry for this confusion. Just updated the message

Darin Douglass17:04:56

(reduce-kv (fn [all key value]
             (assoc all (keyword "bar" (name key)) value)
           {}
           raw)
(->> raw
     (juxt (map (comp (partial keyword "bar") name key)) val)
     (into {}))
something like either of these ^ should work

noisesmith17:04:29

the ->> one needs (juxt ... val) around the comp

👍 4
Darin Douglass17:04:53

heh ya found that out when i decided to actually test it in cider

noisesmith17:04:08

(map (juxt (comp (partial keyword "bar") name key) val)

Darin Douglass17:04:23

could also do

(zipmap (map (comp (partial keyword "bar") name) (keys raw)) (vals raw))

ryan echternacht17:04:25

sometimes my calva -> nREPL connection grinds to a halt (auto formatting fails, the repl itself seems slow). Any thoughts on what’s going on/what I’m doing wrong?

pez18:04:05

@ryan072 I don't think it is you. It is latest Calva. Downgrade to .93 for now. And please PM me if you have done time to help me debug it. I can't reproduce.

nprbst19:04:45

Hello, all you fine Clojurians... I’m new to the Clojure(Script) ecosystem and I’m trying to get my bearings. One thing I’ve noticed as I look around for current state-of-the-art is that it seems many otherwise useful and well-documented libs have their last commits many months or even years ago. Is this an indication of abandonment (as it would’ve be in other ecosystems) or a consequence of the stability of Clojure implementations...as in “it’s working and it will continue working, so no need to touch it”? Any guidance here is much appreciated!

noisesmith19:04:25

new clojure versions very rarely break libraries, so unless something is mainly for interop with a more volatile ecosystem, changes are pretty rare once something works

seancorfield19:04:28

It's also part of the Clojure mindset to a) try to keep the focus of a library small (so you don't get feature creep over time, which also means it can be "done" and need no further updates) and b) avoid breaking changes (fixing bugs is OK, adding features is OK, breaking anything is bad -- so we often use new names for things that are different). @nathan.probst And Welcome!

💯 4
nprbst19:04:23

Thank you. That's what I was hoping to hear!