Fork me on GitHub
#beginners
<
2020-04-18
>
kimim03:04:43

anyone use clojure as research paper data analysis tools?

andy.fingerhut04:04:14

I would recommend asking in the #data-science channel, as there might be a larger audience of people interested in that kind of thing in that channel.

👍 4
Jim Newton08:04:36

Is anyone else constantly greeted with the error: Can't have 2 overloads with same arity in code such as the following?

(defn call-with-rte "docstring"
  ([[] thunk] (thunk))
  ([[odd-man-out] thunk]
   (throw (ex-info (format "odd number of values given as first argument of call-with-rte: %s"
                           odd-man-out)
                   {:type :invalid-call-to-call-with-rte})))
  ([[key value & others] thunk]
   (let [*rte-known* (assoc *rte-known* key value )]
     (call-with-rte others thunk))))

jsn09:04:01

you really should use comment threads, especially when you pasting walls of code

jsn09:04:29

both of your argspecs here match odd number of values, so there's no way for clojure to differentiate between them

jsn09:04:45

also, clojure doesn't even try that, iirc; only arity matters

jsn09:04:27

cf.

user=> (let [[aa bb & c] [:a]] aa)
:a

jsn09:04:55

so built-in pattern matching doesn't do what you are trying to do anyway; if you need more, there's a chance that https://github.com/clojure/core.match/wiki/Overview can help

Jim Newton09:04:13

what is iirc ?

jsn09:04:38

"If I Remember Correctly"

Jim Newton09:04:13

what is a comment thread? and how can I start one?

jsn09:04:53

This place where we talking now is comment thread. it's not, as you can see, in the main channel

Jim Newton09:04:24

and How can I start one of these?

jsn09:04:03

Oh come on 🙂 watch some tutorial on how to use slack? I don't even know what client you use. In web interface, it's in the popup icon bar when you mouseover some message

hindol09:04:21

@U010VP3UY9X Instead of one function with multiple arities, you can also look up defmutli and defmethod.

hindol09:04:07

core.match, as suggested above, can also be a good solution. But that's an extra dependency.

Jim Newton09:04:09

@UJRDALZA5, is defmulti really a good solution, it sounds like a big hammer just to avoid one 3-clause cond.

jsn09:04:45

well, in this particular example the matching thing is really only used to catch incorrect usage; I'd argue that spec should be used for that instead

hindol09:04:49

Regarding starting a thread, post one comment. Click on that posted comment, you should see option for starting a thread.

jsn09:04:15

(or assert or something)

hindol09:04:42

The problem with destructuring is, you can destructure extra values, and the non-matched value all become nil. To Clojure, all overloads have arity 2.

Jim Newton09:04:53

with assert, I don't think I can control the exception which is created. right?

jsn09:04:30

right; when-not <...> throw (ex-info ...) then?

Jim Newton09:04:32

@hindon, yes I see, in my destructuring cases I'm implying an order.

Jim Newton09:04:03

@UTQEPUEH4, that's more or less what my cond-based solution does. Right?

Jim Newton09:04:23

This forces me to parse my own function arguments, which is not pretty:

(defn call-with-rte "docstring"
  [bindings thunk]
  (cond
    (empty? bindings)
    (thunk)

    (empty? (rest bindings))
    (throw (ex-info (format "odd number of values given as first argument of call-with-rte: %s"
                            (first bindings))
                    {:type :invalid-call-to-call-with-rte}))

    :else
    (let [[key value & others] bindings
          *rte-known* (assoc *rte-known* key value)]
     (call-with-rte others thunk))))

hindol09:04:00

I just looked at the cond based solution. Yeah, spec seems to be a great fit here.

hindol09:04:30

But the exception will be same.

hindol09:04:16

The exception will say data does not conform to spec. And you get a nice explanation which part of the data is out of shape.

hindol09:04:51

But otherwise, just go with the cond based solution.

jsn09:04:21

I actually think that assert's or spec exceptions are the right ones, and creating custom exceptions for that is counter-productive

Jim Newton09:04:22

I could simply use (apply assoc *rte-keys* bindings) to give me the same semantics, except that the error on odd-number-of-arguments would be triggered in the call to assoc, rather than int he call to call-with-rte

Jim Newton09:04:09

The other curious thing about the code is that the dynamic rebinding doesn't seem to work.

Jim Newton09:04:23

why doesn't the let rebind the dynamic variable?

jsn09:04:31

also, the whole let / call thing is meaningless, of course; your thunk will not see let bindings

jsn09:04:23

because let creates new lexical bindings, if you want to call your thunk with re-bound dynamics, you need binding

💯 4
Jim Newton09:04:23

yes, i've discovered that. why not? How can I rebind a dynamic variable?

jsn09:04:39

and if you have binding , you just don't need your call-with-rte at all ('cause it doesn't do anything except calling binding anyway, afaics

Jim Newton09:04:47

ok, thanks. I saw the docs for binding but it does not mention dynamic bindings. in CL we use (let ...) for both and the compiler figures out which variables are dynamic and which are special.

Jim Newton09:04:50

@UTQEPUEH4, the recursive call is important as each call simply consumes 2 of the bindings.

jsn09:04:09

I don't get it. why not just, I don't know, (binding [*rte* (apply assoc *rte* your-args)] (your-thunk)) ?

jsn09:04:29

why not actually require a map of those rtes (whatever they are)? as in (defn call-with-rte [thunk rtes] (binding [*rte-known* (merge *rte-known* rtes)] (thunk))) ?

jsn09:04:53

or [thunk & [{:as rtes}]] , if that's your thing (sure isn't mine, though)

Jim Newton10:04:06

my original goal for avoiding multi-arg call to assoc, is because if an odd number of args are passed, then the user gets a confusing error about a call to assoc, rather than about a call to call-with-rte

Jim Newton10:04:52

about using a map directly, it's because call-with-rte is the workhorse function of the macro with-rte, and the syntax of with-rte is like let. You would not like to use let if you had to give it a map, right?

Jim Newton11:04:55

to exploit assoc is just too tempting. Here is what I ended up with.

Jim Newton11:04:36

(defn call-with-rte [bindings thunk]
  (binding [*rte-known* (apply assoc *rte-known* bindings)]
    (thunk)))
      
(defmacro with-rte [bindings & body]
  `(call-with-rte '~bindings (fn [] ~@body)))

Jim Newton11:04:44

minus the docstrings

jsn11:04:49

why not just (defmacro with-rte [bindings body] (binding bindings @body))` ?

Jim Newton12:04:26

I prefer having a macro API and also a functional API for several reasons. it is easier to implement and test the functional API.

Jim Newton09:04:38

This forces me to parse my own function arguments, which is not pretty:

(defn call-with-rte "docstring"
  [bindings thunk]
  (cond
    (empty? bindings)
    (thunk)

    (empty? (rest bindings))
    (throw (ex-info (format "odd number of values given as first argument of call-with-rte: %s"
                            (first bindings))
                    {:type :invalid-call-to-call-with-rte}))

    :else
    (let [[key value & others] bindings
          *rte-known* (assoc *rte-known* key value)]
     (call-with-rte others thunk))))

Jim Newton09:04:46

is let capable of binding dynamic variables in clojure? Or do I need some other special form for that?

Jim Newton09:04:35

or does let only bind lexical variables?

Jim Newton09:04:09

It looks to me like the following behaves quite differently than I expect. If I have a dynamic variable named *foo* then the following still apparently sees the global value, not the rebound value.

(defn bar [x]
  (cond something *foo* ;; still old global value, doesn't see new *foo* binding
        something-else (let [*foo* new-value] (bar (dec x)))))

andy.fingerhut11:04:24

I haven't tested for certain, but I believe let is only for lexical bindings. If you want dynamic bindings, there is the similar-syntax binding

Jim Newton09:04:38

How can I make the recursive call see the new dynamic binding?

didibus10:04:15

let is only for lexical binding.

didibus10:04:25

For dynamic, you want to use binding

Aron10:04:00

some errors are really hard to debug, what does Uncaught TypeError: Cannot read property 'cljs$core$IFn$_invoke$arity$1' of null mean? In the line it says it is by source-map, it seems not to be a null

Aron10:04:26

I know the js error, but that's not enough right now

Aron10:04:00

I can't even do hot-reload because it's inside react and react complains that it's an unmounted component, have to do manual refresh, despite using figwheel and everything

didibus10:04:15

You have a nil somewhere where it isn't allowed

didibus10:04:36

Seems like it might be you try to call something as a function, but that thing is nil

Aron10:04:48

yeah, some props are not there, would be nice to know how to write specs for props so that I catch if one is nil

Aron10:04:07

but these are nested props in dynamically created components, I have no idea how

Aron10:04:34

I found it, but it's the most frequent bug that I waste minutes on before I find it

didibus10:04:07

Hum, ya I don't know. Unfortunately don't have a lot of experience with react and surrounding libs

Aron10:04:47

I am not sure why would this be specific to react and to its libs.

Jim Newton12:04:11

What are the semantics of memoize with regard to recompilation?

(def f (memoize g))
When I re-evaluate this definition, has the old cache been removed/forgotten?

potetm12:04:46

That’s true for all forms. They get re-set on the var whenever you re-evaluate.

potetm12:04:01

You can prevent this with defonce

Aviv Kotek12:04:02

matplotlib equivalent in clj? vega/oz would be enough? is there something else I should look on? I see some cluttered reddit threads but not much more, ty

hindol13:04:44

Ask in #data-science too.

Jim Newton15:04:05

I can get the ancestors of a class with ancestors : (ancestors Long) returns a set of classes, one of which is java.lang.Number . However (descendants java.lang.number) raises an exception.

Execution error (UnsupportedOperationException) at clojure-rte.core/eval18520 (form-init10469598600043035746.clj:682).
Can't get descendants of classes
Is there a way to find these descendants?

Alex Miller (Clojure team)15:04:51

There is no way to ask that question in Java

Alex Miller (Clojure team)15:04:23

Without doing static analysis of “all” classes

Alex Miller (Clojure team)15:04:37

Which is what ides like Cursive do

Jim Newton15:04:56

Cursive, the IDE?

Jim Newton15:04:56

perhaps there's a way to answer an easier questions. Given two classes, are they disjoint? I was planning to answer the question by finding whether they have a common descendant. my idea doesn't work.

Jim Newton15:04:22

I don't actually need to know what the common descendent is, just whether there is one.

Alex Miller (Clojure team)15:04:04

If you’re talking concrete classes, then you only need to know whether one is an ancestor of the other

Alex Miller (Clojure team)15:04:35

If interfaces, then what’s to stop someone from introducing a new interface at any point that implements both?

Jim Newton15:04:41

the question of whether a new class or introduced later doesn't bother me.

Jim Newton15:04:55

but no, I don't know they are both concrete classes. they may be abstract classes.

Jim Newton15:04:09

can I know from clojure whether a class is abstract or not? maybe that could be a workaround. If they are abstract (or interfaces) assume they might intersect, but if they are concrete, determine whether they are disjoint by looking at ancestors

andy.fingerhut16:04:13

Out of curiosity, why do you want to know if two classes A and B are disjoint? (I guess by that you mean that an object cannot be an instance of a subclass of both A and B?)

Jim Newton16:04:37

exactly. given an expression such as (and Integer String) we know this is an empty type. We know this because Integer and String are disjoint. And we for the same reason we know that (and (not Integer) String) is inhabited.

Alex Miller (Clojure team)17:04:07

Integer and String are also final so they can’t be extended

Jim Newton17:04:38

How can I know whether a given class is final?

andy.fingerhut17:04:04

Should be one of the attributes/flags you see on a class using the Java reflection API.

Jim Newton17:04:54

is there some sort of show-me-the-attributes function in clojure?

andy.fingerhut17:04:18

There is a Clojure wrapper around that (or similar JVM calls) that gets you a Clojure data structure with the same data in it. Looking that up ...

andy.fingerhut17:04:45

Try this in a REPL:

(require '[clojure.reflect :as refl])

andy.fingerhut17:04:55

(print (refl/type-reflect java.lang.Integer))

Jim Newton17:04:05

there is a cider-inspect function, but it doesn't seem to do anything useful.

andy.fingerhut17:04:44

I have no knowledge of Cider with which to suggest anything there.

Jim Newton17:04:25

interesting (refl/type-reflect java.lang.Integer) returns a data structure with a key :flags whose value is #{:public :final}

Jim Newton17:04:43

Is a clojure program free to require clojure.reflect ? or will that fail to work in some environments?

Jim Newton17:04:27

Question. given two classes, one of which is final and one not, then may I suppose they are disjoint whenever the non-final one does not have the other in its ancestors?

Bill Phillips17:04:35

Java does not have multiple inheritance for classes

Bill Phillips17:04:30

So yes. Similarly, (and (not (instance? (type a) b)) (not (instance? (type b) a)))

Bill Phillips17:04:53

instance? would be the idiomatic operator here

Jim Newton17:04:00

Bill, you are supposing I have an object of the type, right? or am I missing something?

Jim Newton17:04:32

I'm trying to do a predictive computation without having an instance. i.e., which will for for any instances of the two types.

Bill Phillips17:04:09

ahh, you are correct. although I think Java’s Class has a method to make the check you’re doing by scanning ancestors

Jim Newton17:04:34

@U010GL90FN0 l two classes are disjoint if they have no common inhabited descendent. And as the previous discussion lead me to know, in java you cannot ask for descendants of a class 😞

Jim Newton17:04:25

The work-around is that in the case of a final class, we know there are no descendants. thus we just need to check whether one is a supertype of the other, via ancestor check.

Bill Phillips17:04:08

but remember: there’s no multiple inheritance

Bill Phillips17:04:29

so if two classes are not ancestors of one another, they cannot have a common descendant

Bill Phillips17:04:18

if neither is assignable from the other, they are disjoint, and so are any subclasses

Jim Newton17:04:48

cool. I saw that function in the code for isa?

Bill Phillips17:04:46

ahh, that looks like the tool you want. much easier

Jim Newton17:04:06

I don't understand the logic. Should I just take your word for it? Or should I try to convince myself that you are right?

Bill Phillips17:04:09

if you like. you seem like someone who prefers to know things for sure. the key thing to know is that, for a given class A, it will have exactly one superclass. Object is the root class

Jim Newton17:04:54

How can I call isAssignableFrom ? The isa? code uses a syntax I don't understand. (. ^Class parent isAssignableFrom child)

Jim Newton17:04:06

OK, here is an example. java.lang.constant.Constable and .Serializable both have no ancestors, but they are superclasses of both Integer and also String. The computation should determine that they are NOT disjoint.

Bill Phillips17:04:13

it looks like isa? works on class instances

Bill Phillips17:04:40

Unfortunately, your specific example is not answerable

Jim Newton17:04:30

what does the isAssignableFrom approach do in the case of Constable and Serializable ?

Bill Phillips17:04:11

isa? looks like a nice bit of sugar on top of isAssignableFrom. So they will accomplish the same thing here

Bill Phillips17:04:13

Let’s take (disjoint? .Serializable com.bphillips.Box) as an example

Bill Phillips17:04:32

Box is my class, I defined it

Bill Phillips17:04:50

it doesn’t implement Serializable

Bill Phillips17:04:10

Serializable is an interface, though. Any subclass of Box can implement it if they want

Jim Newton17:04:24

is Box final? I guess it isn't.

Jim Newton17:04:40

OK, so I understand how to compute disjoint-ness in the case one of the classes is final. The remaining question is what is the best I can do when they both fail to be final.

Bill Phillips17:04:27

well, let’s choose something besides Serializable

Bill Phillips17:04:38

if we choose something that is a class, not an interface, the question is easy to answer

Jim Newton17:04:07

Comparable ?

Bill Phillips17:04:27

classes aren’t interfaces. they’re two distinct concepts in Java

Bill Phillips17:04:16

if you want to refer to both, “type” would be the right word I think

Jim Newton17:04:54

it looks like the (refl/type-reflect java.lang.Comparable) tells me whether the given class is an interface. thats good.

Bill Phillips17:04:04

classes aren’t interfaces

Bill Phillips17:04:07

no class is an interface

Jim Newton17:04:09

ok there are three cases, :final, :interface, and other.

Jim Newton17:04:32

so there are 6 cases for disjoint?` right? some of which are answerable and some not.

Jim Newton17:04:06

So what can I know if one of the two classes is not an interface and also not final ?

Bill Phillips17:04:10

i think there are only four cases: :interface :interface :interface :class :interface :final-class :class :class

Jim Newton17:04:56

btw, (type java.lang.Comparable) returns java.lang.Class so programmatically speaking it is a class.

Jim Newton17:04:28

(type Integer) returns the same thing, as does (type Object) so from the clojure perspective, they are all classes, even if at the java level they are not.

Bill Phillips17:04:34

sure. but that is a detail of the reflection. in Java code, these things are declared by writing either public class MyClass { ... } or public interface MyInterface { ... }. Extending a class is done with extends, extending an interface is done with implements.

Bill Phillips17:04:07

if you abide by this distinction, i can promise you that it will pay dividends in your communications with all who are fluent in Java.

Bill Phillips17:04:53

and this is also why clojure’s decision to name the function to get an instance of Class type rather than class was a good one. 🙂

Bill Phillips17:04:11

(now that I think about it)

Jim Newton17:04:32

so there are three cases for a and three cases for b , :final, :interface, :else. That is 9 cases. if either is :final, we can use isa?. that leaves 4 cases. (other, interface), (other, other), (interface, interface), (interface other)

Jim Newton17:04:07

I think for (other,other) I can use isa? because of the promise of single inheritance. nothing can inherit from both unless one is already a subclass of the other.

Bill Phillips17:04:21

anyway, to get back to the four categories: * :interface :interface - These are never disjoint. Nothing prevents someone from creating a new class at runtime that implements both interfaces. * :interface :class - Same as above. Nothing prevents someone from creating a new subclass that implements the interface. :interface :final-class — Disjoint if (isa? interface final-class) (edit: I mean (not (isa? interface final-class)) :class :class — Disjoint if (not (or (isa? a b) (isa? b a))

Jim Newton17:04:31

That leaves 3 (other,interface),(interface,other), and(interface,interface)

Bill Phillips17:04:24

And if the right hand is an interface and the left hand is a class, swap them

Jim Newton17:04:28

I think it makes sense. I'll try to code it up tomorrow, and see what happens. its almost 20h00 now. time to quit for the day.

👍 4
Jim Newton17:04:57

@U010GL90FN0 thanks for the insight. I'll site (blame) you in the comments of my function, and everyone will send you email if it breaks (grins).

Jim Newton17:04:22

how should I cite you?

Bill Phillips17:04:28

hope i was of some help! i have NEVER explained Java’s notion of classes to someone who was introduced to OO elsewhere, or through clojure’s reflection first, so this is interesting. 🙂

Bill Phillips17:04:49

Bill Phillips is fine. @jingibus on github

Jim Newton17:04:05

Java's concept of type of class is bizare to someone coming from lisp.

Bill Phillips17:04:12

It is very, very restricted, by design. Classes are very concrete and inflexible. But it is helpful to understand them, because every instance in Java is built on that foundation

Jim Newton17:04:22

for me a type is a set of values. the type might or might not have a name, and might or might not be specifyable.

Jim Newton17:04:50

OK. gotta go. thanks again for the help.

Bill Phillips20:04:01

made a mistake above, see edit

Jim Newton09:04:41

I have discovered 4 kinds of class flag sets. I don't understand the difference, as I don't know java. Maybe these make sense to a Java programmer???

clojure-rte.core> (:flags (refl/type-reflect  String))
#{:public :final}
clojure-rte.core> (:flags (refl/type-reflect  Object))
#{:public}
clojure-rte.core> (:flags (refl/type-reflect  Number))
#{:public :abstract}
clojure-rte.core> (:flags (refl/type-reflect java.lang.CharSequence ))
#{:interface :public :abstract}
clojure-rte.core> 

Jim Newton09:04:03

what is a public class which is neither abstract not final ?

Jim Newton09:04:17

are all interfaces also abstract?

Jim Newton09:04:49

Also two very different classes Object and clojure.lang.PersistentList are both marked as :public but neither :final nor :abstract

Jim Newton09:04:20

what does it mean to be non-abstract? and non-interface? does that mean it is possible to instantiate it?

Jim Newton16:04:36

in terms of code optimization this means that if we have already checked that an object is an Integer and discovered that it is then we know a subsequent check for whether it is a String can be omitted as it is guaranteed to evaluate to false .

andy.fingerhut16:04:03

Is your goal to use such knowledge for code optimization?

andy.fingerhut17:04:12

I am not certain, as I have not delved deeply into this kind of thing before, but the JVM has some fairly dynamic features, and I think it may be possible to reload a class later after it has been loaded once before, and if so, the answers might be different for a later loaded version of the class.

andy.fingerhut17:04:30

That might be beyond what you are hoping to handle, if it is true.

Jim Newton17:04:30

semantically it of course does not hurt to check (instance? String x) discover that it is true and thereafter also check (instance? Integer x) , but a static analysis should be able to determine that the latter will be false

Jim Newton17:04:29

how can I call the next most applicable method in a multimethod?

phronmophobic18:04:32

the code for multifns isn’t too bad. as alex said, you could ask for method of choice (using https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/MultiFn.java#L144) and remove it. alternatively, you could copy the https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/MultiFn.java#L161 and change it to return the second best method

Jim Newton09:04:24

Yes, but that only solves the problem the first time. If that second method then wants to call call-next-method, then I'd want it to work as well. In CLOS any method may call call-next-method and it will work, provided there is a next method. There is a function next-method-p if the method wants to know whether there is such a next method.

Jim Newton17:04:46

i.e., the one that would have been called if the current method didn't exist

Alex Miller (Clojure team)17:04:06

You could remove the one it found and call it again :)

Jim Newton10:04:43

removing it and calling again would only work the first time, but it wouldn't work when that method wants to call call-next-method. 😞

Jim Newton10:04:38

CLOS is beautiful for the programmer, but a horror for the compiler implementor.

Jim Newton10:04:45

so what's the pattern using clojure multimethods to allow a method to contribute to the return value, or for a method based on runtime logic to decide whether it can compute the result, or delegate to next less specific method?

Alex Miller (Clojure team)17:04:24

But really, there is no easy way to do that

dev.4openID18:04:56

(defn AAAAAAAA [strx] (try (djson/read-str (:body (client/get str) {:body-encoding "UTF-8" :accept :json }}))) (catch Exception e (prn e)) )) This piece of code fetches some data; however the exact data might not be there so it fails. For some reason the error handling does not catch the error. (meaning the e value) I sometimes get an error crash dump and the error JSON is reflected on the crash dump - I can read it and it provides the 404 code I modelled this from the clojuredocs and it does not work -why not? I assume the catch line will not require a djson/read-str as it is a header dump - I cannot find anything that makes sense of this. Using clj-http

hindol18:04:06

What happens when you change to (catch Throwable ...)?

kelveden19:04:18

Putting aside the reason for the error not being caught for a moment, there are a couple of things I can see wrong with that code. Firstly, if it's clj-http you're using, that options map needs to be the second arg to client/get. Secondly, the first arg to client/get is str - that's a clojure function. I assume what you meant to use is strx instead.

dev.4openID19:04:32

you are perhaps right however the client/get is a redacted statement and as is it works and I get answers. However, when the dta is set to fail I should get a 404 and the catch does not work (this is the focus of the question)

dev.4openID19:04:06

It is a (try (client /get function) (catch e) fundamentally. Yet the catch does not trigger on the 404 error. Am I supposed to use something else to catch the 404?

dev.4openID20:04:35

clojure.lang.ExceptionInfo: clj-http: status 404 {:cached nil, :request-time 1036, :repeatable? false, :protocol-version {:name "HTTP", :major 1, :minor 1}, :streaming? true, :http-client #object[org.apache.http.impl.client.InternalHttpClient 0x61213e02 "org.apache.http.impl.client.InternalHttpClient@61213e02"], :chunked? false, :type :clj-http.client/unexceptional-status, :reason-phrase "Not Found", :headers {"access-control-max-age" "3628800", "access-control-allow-headers" "origin,x-requested-with,accept,content-type,xxxxxx-api-key", "Content-Type" "application/problem+json", "access-control-allow-origin" "*", "Content-Length" "97", "Connection" "close", "access-control-allow-methods" "GET,OPTIONS", "Date" "Sat, 18 Apr 2020 20:26:42 GMT", "Content-Language" "en", "Correlation-Id" "2b10c2b7-8b61-4a60-9ee3-9f57f886c0fe"}, :orig-content-encoding nil, :status 404, :length 97, :body "{\"title\":\"No result found\",\"status\":404,........etc It seems that the e details not structured. If I want to chack the status (it being 404) am I to just parse the "string"?

dev.4openID20:04:53

I mean my objective is to manage 404 and other error sop the system can recover properly

kelveden21:04:11

As it's a ExceptionInfo, ex-data is your friend here. Use it to pull out that map of data from the ExceptionInfo. Then you can interrogate that map using the usual clojure functions - e.g. you can read off the status code and handle it.

kelveden21:04:47

So you could then have a case or cond in your catch handler to deal with different status codes.

seancorfield21:04:21

Another option is to change the clj-http to not throw exceptions for non-success HTTP codes and then you don't have to deal with try/`catch` and you just check the :status field of the client/get response.

seancorfield21:04:46

I usually find that's a far easier way to deal with 404 and other HTTP codes.

4
seancorfield21:04:38

:throw-exceptions false

kelveden23:04:28

Yes, that's certainly a simpler way of doing it and the one I'd use by default too. You then need to be careful that you're covering all "exceptional" status codes in your response handling code otherwise you could end up with some potentially cryptic errors.

dev.4openID14:04:48

Well after learning about it (I am not a Java prog) I have managed to extract the data. My catch code is now (catch clojure.lang.ExceptionInfo e (djson/read-str (str (:body (ex-data e))))))) It returns the map I want Thanks @UJF10JP8A and @U04V70XH6 Woking on the huge learning curve! 😁

hindol15:04:54

I checked that ExceptionInfo is a subclass of RuntimeException which is a subclass of Exception. Does anyone know why catching Exception does not work?

seancorfield17:04:09

Catching an ex-info via Exception works for me:

user=> (try
         (throw (ex-info "test" {:a 1}))
         (catch Exception e
           (println "caught" (ex-message e) (ex-data e))))
caught test {:a 1}
nil

pj22:04:06

Hi! Does anyone know of any boards or w/e where people/beginners share sth they've recently written and more experienced people take a look at it for feedback or sth to that effect?

phronmophobic23:04:57

there’s the #code-reviews channel, but it’s usually slow. I haven’t tried it, but something like https://exercism.io/tracks/clojure may be of interest. you can ask in #exercism if you want a take from someone who actually has tried it

runswithd6s03:04:50

This is really great! Thanks @U7RJTCH6J