Fork me on GitHub
#beginners
<
2022-04-20
>
mister_m00:04:23

I have a let block where I am defining a binding for the result of com.data.xml's parse-str for further processing. This throws an exception if the xml string is junk. Is it ideomatic to wrap the init form of the binding in a try in order to handle that potential exception? It seems a little kludgy

seancorfield00:04:12

I'd say it very much depends on what you want your code to do when fed junk XML...

seancorfield00:04:20

...and also whether you might want to handle other exceptions from processing (valid) XML separately from that junk exception.

mister_m00:04:33

That is a fair point

seancorfield00:04:07

It might be reasonable to do something like this:

(if-let [data (try (parse-str input) (catch Throwable t (log/error t "bad XML input")))]
  (do-stuff-with data)
  (report-error-to-user))
(although giving them some idea of what went wrong would probably be better).

mister_m00:04:13

The context surrounding the question is that I am writing an API controller, and really just need to trap an exception and return a bad request in the event I'm unable to parse something

mister_m00:04:28

ah if-let is a good idea

seancorfield00:04:36

Ah, OK, then this would be better IMO:

(try
  (let [data (parse-str input)]
    (do-stuff-with data)) ; and return success response?
  (catch Throwable t
    (error-response (ex-message t)))) ; or something similar

seancorfield00:04:10

For a lot of cases, having exception-handling middleware that logs the exception and responds with a 4xx or 5xx status error is enough -- and then you can just let exceptions bubble up from your controller code.

mister_m00:04:06

Thanks sean

seancorfield00:04:05

(defn exception-middleware
  [handler]
  (fn [req]
    (try (handler req)
      (catch Throwable t
        (log/error t "request failed")
        {:status 500 :body (ex-message t)}))))
(although beware of leaking sensitive information through an exception's message -- probably better to just respond with a default message after logging the exception)

seancorfield00:04:34

So... lots of possibilities, depending on what you need/want 🙂

seancorfield00:04:58

We try/`catch` some exceptions close to the source for better logging/reporting context, but we also have middleware like the above to catch any otherwise unhandled exceptions, log them, and return a generic message.

seancorfield00:04:49

We actually put a UUID into req inside that middleware and log it with the exception and report it back to the caller. That way then can contact us and provide a unique identifier that we can track down in the log files.

mister_m00:04:05

That makes sense

noisesmith06:04:41

aside, calling it an "init form" is weird when the value can't be mutated, the normal name is "binding form"

Serafeim Papastefanos07:04:21

hello friends. I'm creating an uberjar for my simple app using clj -T:build uber ... In my deps.edn I've got the following:

{:build {:deps {io.github.clojure/tools.build {:git/tag "v0.8.1" :git/sha "7d40500"}}
          :ns-default build}}}
and my build.clj is like this:
(ns build
  (:require [clojure.tools.build.api :as b]))

(def class-dir "target/classes")
(def basis (b/create-basis {:project "deps.edn"}))
(def uber-file "target/cljcrd-0.0.1.jar" )

(defn clean [_]
  (b/delete {:path "target"}))

(defn uber [_]
  (clean nil)
  (b/copy-dir {:src-dirs ["src" "resources"]
               :target-dir class-dir})
  (b/compile-clj {:basis basis
                  :src-dirs ["src"]
                  :class-dir class-dir})
  (b/uber {:class-dir class-dir
           :uber-file uber-file
           :basis basis
           :main 'cljcrd.core}))
Now, the uberjar is created and runs without problems. However it takes a really long time for it to be created. Like 1 minute. Any idea why ? Can I make this faster somehow ?

p-himik13:04:43

Have you tried putting some time-reporting logging in between the statements in uber to see what step exactly takes most of the time? My guess is that it's compile-clj. Compilation can take a long time since it compiles not just your sources but also all of the dependencies of those sources. If you don't need AOT, you can skip compilation. And if you do need AOT, compile only the namespaces that need it.

Serafeim Papastefanos14:04:06

Hmmm thank you. So it recompiles all dependencies every time?

p-himik14:04:55

If you don't remove target/classes and don't change anything, it shouldn't *re*compile - it will skip everything that's already compiled.

Serafeim Papastefanos14:04:56

That was my understanding... However it takes a lot of time every time :(

leifericf08:04:36

I got the opportunity to use Clojure for the first time at work yesterday, if only as an advanced calculator. Scenario: A businessperson (CRM specialist) came to me with a trivial math question: “I sent 223000 surveys last week and got 1500 responses. How many surveys do I need to send next week to get 250 responses?” Their rationale was not to spam customers unnecessarily. I tried to explain it using only words first, but that didn’t work so well. Then I tried to explain it using pen and paper, which also didn’t work so well. So I decided to fire up a “standalone REPL” in Visual Studio Code with Calva
 And wrote this little code snippet while they watched:

;; First, we need to find the response rate.

(defn get-response-rate [surveys responses]
  (/ surveys responses))

;; Then, we can use the response rate to figure out how many surveys you need to send.

(defn get-required-surveys [response-rate desired-responses]
  (* desired-responses response-rate))

;; When we combine those steps with your numbers, we find the answer.

(-> (get-response-rate 223000 1500) ;; => 446/3
    (get-required-surveys 250) ;; => 37166.688
    (int)) ;; => 37166
Their eyes widened as I was able to use Calva’s Shift + Option + Enter keyboard shortcut to evaluate each “step” of the threading macro to see the result. Creating “verbs” as functions and stringing them together with threading macros—evaluating each “step” in the REPL—is a powerful “scratchpad/whiteboard” for showing and explaining things to non-technical people. Had they asked a few months ago, I would have used Python in Jupyter Notebook. And they were in awe of Clojure/VS Code/Calva, saying things like: “What is that tool you’re using?! That’s amazing. I need something like that.” Reminder to self: Show, don’t tell. And don’t assume that non-technical people won’t understand what’s going on.

calva 8
💯 7
metal 6
clojure-spin 8
pez09:04:13

I wonder why you get that intermediate floating point results... That seems just wrong. I get this:

leifericf10:04:19

I get the same result as you. I made a human copy/paste error in the comment behind that line. At first, I was casting to float in the function get-required-surveys, but I changed that and forgot to update the comment. It was like this at first until I discovered that casting to float was unnecessary:

(defn get-required-surveys [response-rate desired-responses]
  (float (* desired-responses response-rate)))

leifericf10:04:04

Maybe this would have been even better?

(defn get-response-rate [surveys responses]
  (/ surveys responses))

(defn get-required-surveys [response-rate desired-responses]
  (int (Math/ceil (* desired-responses response-rate))))

(-> (get-response-rate 223000 1500)
    (get-required-surveys 250)) ;; => 37167
Because it’s not possible to send a fraction of a survey 😅

pez10:04:30

I prefer to have functions like this non-lossy. If you send them floats, they will return floats. Maybe like so would both demonstrate that we can have it non-lossy and that we can move to floating point land if we want:

(-> (get-response-rate 223000 1500) ; => 3/446
    (float) ; => 0.0067264573
    (get-required-surveys 250) ; => 37166.668
    (int)) ; => 37166

👍 2
leifericf10:04:48

Yeah, I suppose it makes sense to push the “compression” as far as possible down the pipeline. And make it explicit in the threading macro. Thanks for making that point!

leifericf10:04:14

Who would have thought there were so many opportunities for teaching and learning in such a trivial problem/code example 😅

leifericf10:04:30

To get the rounding in there as well:

(defn get-response-rate [surveys responses]
  (/ surveys responses))

(defn get-required-surveys [response-rate desired-responses]
  (* desired-responses response-rate))

(-> (get-response-rate 223000 1500)
    (get-required-surveys 250)
    (Math/ceil)
    (int))

leifericf10:04:17

And maybe it’s a bit tidier to name the “magic numbers” with bindings in a let block?

(defn get-response-rate [surveys-sent responses-received]
  (/ surveys-sent responses-received))

(defn get-required-surveys [response-rate responses-needed]
  (* responses-needed response-rate))

(let [surveys-sent 223000 responses-received 1500 responses-needed 250]
  (-> (get-response-rate surveys-sent responses-received)
      (get-required-surveys responses-needed)
      (Math/ceil)
      (int)))

leifericf11:04:28

Ir simply this, without writing any custom functions 😅

(let [surveys-sent 223000 responses-received 1500 responses-needed 250]
  (-> (/ surveys-sent responses-received)
      (* responses-needed)
      (Math/ceil)
      (int)))

pez11:04:39

I think your colleagues would have understood a bit less this way. But sure, very readable to me. 😃

👍 1
leifericf11:04:15

Yeah, I think so too. Creating custom “verbs” helps in the explaining.

Yogesvara Das10:04:46

Hello. I'm using reframe with primereact. I'm trying to pass a template function to a react component so I can have more granular control of what's rendered but what I have right now causes me to get a blank page. https://www.primefaces.org/primereact/tree/templating/

(defn node-template [node options]
    [:p "test"])

(defn main-panel []
  ;...
  [:> Tree {:filter "lenient"
            :value value
            :nodeTemplate node-template}])
I'm not sure why this is and can't figure this out form the docs I've looked at. Do I have to do something special to use hiccup this way? EDIT: template function body just needs to be wrapped in a reagent.core/as-element call

Ferdinand Beyer11:04:25

Your function node-template by itself just returns a ClojureScript vector. React does not know what to do with that. You need to wrap it with as-element so that Reagent can turn it to a react component. See: https://github.com/reagent-project/reagent/blob/master/doc/InteropWithReact.md

Yogesvara Das11:04:44

Thank you. I figured it out just as you sent that. 🙂