Fork me on GitHub
#beginners
<
2020-04-18
>
kimi.im03: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.

jimka.issy08: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))))

jimka.issy08: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))))

jason35809:04:01

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

jason35809:04:29

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

jason35809:04:45

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

jason35809:04:27

cf.

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

jason35809: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

jimka.issy09:04:13

what is iirc ?

jason35809:04:38

"If I Remember Correctly"

jimka.issy09:04:13

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

jason35809:04:53

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

jimka.issy09:04:24

and How can I start one of these?

jason35809: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

hindol.adhya09:04:21

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

hindol.adhya09:04:07

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

jimka.issy09:04:09

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

jason35809: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

hindol.adhya09:04:49

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

jason35809:04:15

(or assert or something)

hindol.adhya09: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.

jimka.issy09:04:53

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

jason35809:04:30

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

jimka.issy09:04:32

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

jimka.issy09:04:03

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

jimka.issy09: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))))

hindol.adhya09:04:00

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

hindol.adhya09:04:30

But the exception will be same.

hindol.adhya09: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.

hindol.adhya09:04:51

But otherwise, just go with the cond based solution.

jason35809:04:21

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

jimka.issy09: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

jimka.issy09:04:09

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

jimka.issy09:04:23

why doesn't the let rebind the dynamic variable?

jason35809:04:31

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

jason35809:04:23

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

jimka.issy09:04:23

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

jason35809: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

jimka.issy09: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.

jimka.issy09:04:50

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

jason35809:04:09

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

jason35809: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))) ?

jason35809:04:53

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

jimka.issy10: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

jimka.issy10: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?

jimka.issy11:04:55

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

jimka.issy11: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 [] [email protected])))

jimka.issy11:04:44

minus the docstrings

jason35811:04:49

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

jimka.issy12: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.

jimka.issy09: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))))

jimka.issy09:04:46

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

jimka.issy09:04:35

or does let only bind lexical variables?

jimka.issy09: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

jimka.issy09: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

ashnur10: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

ashnur10:04:26

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

ashnur10: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

ashnur10: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

ashnur10:04:07

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

ashnur10: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

ashnur10:04:47

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

jimka.issy12: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

aviv12: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

hindol.adhya13:04:44

Ask in #data-science too.

jimka.issy15: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?

alexmiller15:04:51

There is no way to ask that question in Java

alexmiller15:04:23

Without doing static analysis of “all” classes

alexmiller15:04:37

Which is what ides like Cursive do

jimka.issy15:04:56

Cursive, the IDE?

jimka.issy15: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.

jimka.issy15:04:22

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

alexmiller15:04:04

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

alexmiller15:04:35

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

jimka.issy15:04:41

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

jimka.issy15:04:55

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

jimka.issy15: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?)

jimka.issy16: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.

alexmiller17:04:07

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

jimka.issy17: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.

jimka.issy17: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))

jimka.issy17: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.

jimka.issy17:04:25

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

jimka.issy17:04:43

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

jimka.issy17: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?

jings.bill17:04:35

Java does not have multiple inheritance for classes

jings.bill17:04:30

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

jings.bill17:04:53

instance? would be the idiomatic operator here

jimka.issy17:04:00

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

jimka.issy17: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.

jings.bill17: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

jimka.issy17:04:34

@ 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 😞

jimka.issy17: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.

jings.bill17:04:08

but remember: there’s no multiple inheritance

jings.bill17:04:29

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

jings.bill17:04:18

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

jimka.issy17:04:48

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

jings.bill17:04:46

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

jimka.issy17: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?

jings.bill17: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

jimka.issy17:04:54

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

jimka.issy17: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.

jings.bill17:04:13

it looks like isa? works on class instances

jings.bill17:04:40

Unfortunately, your specific example is not answerable

jimka.issy17:04:30

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

jings.bill17:04:11

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

jings.bill17:04:13

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

jings.bill17:04:32

Box is my class, I defined it

jings.bill17:04:50

it doesn’t implement Serializable

jings.bill17:04:10

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

jimka.issy17:04:24

is Box final? I guess it isn't.

jimka.issy17: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.

jings.bill17:04:27

well, let’s choose something besides Serializable

jings.bill17:04:38

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

jimka.issy17:04:07

Comparable ?

jings.bill17:04:27

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

jings.bill17:04:16

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

jimka.issy17:04:54

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

jings.bill17:04:04

classes aren’t interfaces

jings.bill17:04:07

no class is an interface

jimka.issy17:04:09

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

jimka.issy17:04:32

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

jimka.issy17:04:06

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

jings.bill17:04:10

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

jimka.issy17:04:56

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

jimka.issy17: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.

jings.bill17: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.

jings.bill17: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.

jings.bill17: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. 🙂

jings.bill17:04:11

(now that I think about it)

jimka.issy17: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)

jimka.issy17: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.

jings.bill17: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))

jimka.issy17:04:31

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

jings.bill17:04:24

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

jimka.issy17: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.

jimka.issy17:04:57

@ 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).

jimka.issy17:04:22

how should I cite you?

jings.bill17: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. 🙂

jings.bill17:04:49

Bill Phillips is fine. @jingibus on github

jimka.issy17:04:05

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

jings.bill17: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

jimka.issy17: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.

jimka.issy17:04:50

OK. gotta go. thanks again for the help.

jings.bill20:04:01

made a mistake above, see edit

jimka.issy09: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> 

jimka.issy09:04:03

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

jimka.issy09:04:17

are all interfaces also abstract?

jimka.issy09:04:49

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

jimka.issy09:04:20

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

jimka.issy16: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.

jimka.issy17: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

jimka.issy17:04:29

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

smith.adriane18: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

jimka.issy09: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.

jimka.issy17:04:46

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

alexmiller17:04:06

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

jimka.issy10: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. 😞

jimka.issy10:04:38

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

jimka.issy10: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?

alexmiller17: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

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

hindol.adhya18: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 "[email protected]"], :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.

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 @ and @ Woking on the huge learning curve! 😁

hindol.adhya15: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

piegie22: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?

smith.adriane23: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

gchewie03:04:50

This is really great! Thanks @