Fork me on GitHub
#clojure
<
2022-01-19
>
Dan Maltbie02:01:52

I have a project with a package (p) i've written in Clojure that has several modules (a.clj , b.clj, etc) and want to call it in Java. First confusion is that I have to instantiate a new Java object of the function (p.a/foo) and obj.invoke it. That works but fails when the first function tries to call a second function (p.b/bar) with the following exception.

Caused by: java.lang.IllegalStateException: Attempting to call unbound fn: #'p.b/bar 
Is there some initialization of clojure and the package that needs to happen before I call a function? Why do I have to get an object and invoke to call it?

hiredman03:01:59

That is not the way to do it

hiredman03:01:31

Have you seen https://clojure.github.io/clojure/javadoc/? It has some examples using the API that clojure exposes for java

hiredman03:01:42

The way to do it is to use Clojure.var to get clojure.core/require, use require to load whatever namespace you want, then use Clojure.var to get the functions you want from that namespace

Dan Maltbie06:01:11

Thanks for the quick response. Your references are helpful. I've looked at other similar examples from my google searching. My message was not clear in expressing my issue. I am doing what is shown in the clojure/javadoc.

IFn plus = Clojure.var("clojure.core", "+");
plus.invoke(1, 2);
The difference is that the function I invoke calls a function in another namespace which fails with the unbound error. The call from Java to Clojure works but the function calls a function in another file which fails. Is there some kind of Clojure runtime that is able to perform the equivalent of the Clojure.var .invoke()?

Dan Maltbie06:01:57

All of the examples I've seen show a function that does not reference code in another file. My simple code example is below. Java file contains:

IFn foo = Clojure.var("mod.a", "foo");
foo.invoke(1, 2);
file mod/a.clj contains:
(ns mod.a
  (:require [mod.b])
  (gen-class)
  )

(defn foo [a b]
  (mod.b/bar a b)
  )
file mod/b.clj contains:
(ns mod.b
  (gen-class)
  )

(defn bar [a b]
  nil
  )
Failure occurs when foo tries to call bar

hiredman03:01:36

http://Insideclojure.org might have a more indepth example (I've seen one somewhere, but don't entirely recall where)

emccue03:01:17

i’ve done this extensively recently

Dan Maltbie06:01:47

Thanks for the reference to the gist. I will study it. I guess I expected something simpler. Calling Clojurescript from Javascript required only include extern in the defn. I assumed it would be just as easy for Java.

emccue14:01:05

I mean, fundamentally it is simple

emccue03:01:07

so that should be a decent example of how you can do the wrapping

1
borkdude10:01:12

Some important info on periods in keywords here. It seems to be allowed. The docs need updating.

borkdude11:01:35

I bet you could make some nice ascii art this way.

:...<>...

borkdude11:01:01

Hope I inspired you to make another tweet-deffable lib!

lassemaatta11:01:32

^ perhaps something that encodes normal keywords into morse code keywords, :sos -> :...---...?

💯 1
Tero Matinlassi11:01:22

Making Clojure resemble some esoteric languages (like brainf*) 😂

Alex Miller (Clojure team)13:01:41

Can you add an issue on clojure-site?

borkdude13:01:50

will do now

lread14:01:38

Awesome sleuthing @U04V15CAJ!

borkdude14:01:39

Sleuthing done by @U2FRKM4TW :)

🔍 1
lread14:01:47

Awesome sleuthing @U2FRKM4TW!

🔎 1
noisesmith15:01:52

there's some web related lib (I forget which) that uses keywords like : in tests

borkdude15:01:59

that's bad :)

Alex Miller (Clojure team)15:01:30

data.xml does some interesting stuff like that :)

borkdude15:01:39

but I cannot deny that I also encountered this in a project which keywordized RDF URIs 😂

borkdude15:01:23

the issue we ran into with clj-kondo analysis was that we stored info about keywords, like this, given keyword :1.9 , {:name 1.9 ;;<- symbol ...} so when you would read back in that EDN, then the name value would not be a symbol. We should have stored this as a string anyway, but that's when an "invalid" keyword tripped us up.

quoll18:01:51

@U0178V2SLAY This is just for you ❤️ https://github.com/quoll/remorse

😄 4
❤️ 3
3
borkdude18:01:10

Amazing. I just had to try this with babashka.

$ bb -cp src -e "((requiring-resolve 'kmorse.core/keyword->morse) :foobar)"
:..-._---_---_-..._.-_.-.

p-himik19:01:10

Were it my call, I'd probably name the repo remorse. :D Only because those keywords look terrifying.

borkdude19:01:30

If it were up to me, I would call the lib :-.-_--_---_.-._..._.

1
quoll19:01:37

I’m sorry… I was focusing my creativity on creating the monster, not naming it 😊

quoll19:01:47

I agree… “remorse” would be a better name

quoll19:01:28

I renamed it, and updated the link above 🙂

Alex Miller (Clojure team)19:01:08

you are all bad people

🙏 3
noisesmith00:01:23

@U051N6TTC that reminds me of my encoder that disguises a plaintext to look like homerow keyboard mashing https://gist.github.com/noisesmith/561c215ffa39f71645b5

user> (base-_SEMI_lkjfdsa-encode "hello")
";ddks;jjfddas;;;"

😊 1
Pavel Mugyrin13:01:11

Hey everyone! Is it possible to write a "higher-order" transducer which takes another transducer and transform a specific key in a map with it. For example, I want to calculate a moving average in a series of maps like this:

(def values
  [{:ix 0, :val 0.1009376937865758}
   {:ix 1, :val 0.4809050647549886}
   {:ix 2, :val 0.9892799406144329}
   {:ix 3, :val 0.017281836069744982}
   {:ix 4, :val 0.8858032296740233}
   {:ix 5, :val 0.9732218596402712}
   {:ix 6, :val 0.4625438903362259}
   {:ix 7, :val 0.9466645843710969}
   {:ix 8, :val 0.5040162806448868}
   {:ix 9, :val 0.7839764149216033}])

(require '[net.cgrand.xforms :as x])

;; transducer to calculate ma
(defn ma [n]
  (x/partition n 1 x/avg))

;; get raw ma
(sequence (comp 
           (map :val)
           (ma 5)) values)

;; => [0.49484155297995314 0.6692983861506921 0.6656261512669397 0.6571030800182724 0.7544499689333009 0.7340846059828168]

;; what I want is
[{:ix 4 :val 0.49484155297995314}
 {:ix 5 :val 0.6692983861506921}
 {:ix 6 :val 0.6656261512669397} 
 {:ix 7 :val 0.6571030800182724}
 {:ix 8 :val 0.7544499689333009}
 {:ix 9 :val 0.7340846059828168}]

Pavel Mugyrin13:01:28

Something like:

(defn higher-order-update
  [key xfrom]
  (fn [rf]
    (fn
      ([] (rf))
      ([result] (rf result))
      ([result input]
       (rf result (assoc input key
                         (get input key) ;; <-- stick xform somwhere here???
                         ))))))
Can't wrap my head around this...

Joshua Suskalo14:01:51

(defn map-key
  [k xform]
  (let [xf (xform (completing #(assoc %1 k %2))]
    (fn [rf]
      (fn
        ([] (rf))
        ([result] (rf result))
        ([result input]
         (rf result (xf input (get input k))))))))

Joshua Suskalo14:01:12

I haven't tested this at a repl, but this is approximately correct I think. It should be checking for reduced and similar though

Joshua Suskalo14:01:17

plus it should call the completing part of xf too

Pavel Mugyrin14:01:25

checking for reduced like this?

(defn- multiplexable
  "Returns a multiplexable reducing function (doesn't init or complete the uderlying rf, wraps reduced -- like preserving-reduced)"
  [rf]
  (let [rf (ensure-kvrf rf)]
    (kvrf
     ([])
     ([acc] acc) ; no init no complete rf
     ([acc x]
       (let [acc (rf acc x)]
         (if (reduced? acc)
           (reduced acc)
           acc)))
     ([acc k v]
       (let [acc (rf acc k v)]
         (if (reduced? acc)
           (reduced acc)
           acc))))))

Joshua Suskalo14:01:13

yeah. If you give me a couple minutes I'll have access to a computer and can give a more complete and correct example

Pavel Mugyrin14:01:00

Thank you very much! I will try it out tonight !

Joshua Suskalo15:01:52

Okay @UTTHNCF5L I've got a couple minutes to work on this and I have a question now: how should this act when the transducer produces multiple values, or none?

Joshua Suskalo15:01:51

Or is this a very limited usecase transducer that requires that xform be a stack that produces one value for each result with an optional reduced? result at the end?

Pavel Mugyrin15:01:07

I think my tranducers are expected to produce exactly one value per map in the series. But in more general case I think we should just duplicate items from the series or omit them. For example if xform is going to be (filter #(> % 0.5) than just omit all the maps where the :val is less than 0.5

Joshua Suskalo16:01:19

So is there a particular reason you're looking for a transducer that operates on maps rather than doing something like this? Just to make it a little shorter?

(filter (comp #(> % 0.5) :the-key))

Joshua Suskalo16:01:10

(defn key-xf
  [k xform]
  (fn [rf]
    (let [xf (xform conj)
          last-map (volatile! nil)]
      (fn
        ([] (rf))
        ([result]
         (try
           (rf (if (nil? @last-map)
                 result
                 (reduce #(unreduced (rf %1 (assoc @last-map k %2)))
                         result
                         (xf []))))
           (finally
             (vreset! last-map nil))))
        ([result input]
         (vreset! last-map input)
         (let [res (xf [] (get input k))]
           (cond-> (reduce #(rf %1 (assoc input k %2))
                           result
                           (unreduced res))
             (reduced? res) ensure-reduced)))))))
There's a more complete version of it

Joshua Suskalo16:01:49

handles reduced, and when the xf produces one value or more than one. It handles producing more than one value by adding the same map multiple times with different values for the key

Pavel Mugyrin17:01:56

Works like a charm! Thank you very much once again!

Pavel Mugyrin17:01:43

> So is there a particular reason you're looking for a transducer that operates on maps rather than doing something like this? Just to make it a little shorter? There might be two reasons. First is to decouple the logic of filtering values from the specific position inside the map. Second, most of the time transducers need to map something (and often do aggregations like with moving average example)

Joshua Suskalo17:01:34

Sure. Just be careful with aggregations because as I said, only the map paired with the last item before it emits an item will be assoc'ed into. Which won't be a problem if you're just keeping an aggregate and emitting on each input, but with e.g. partition-all it acts a little strangely.

Joshua Suskalo17:01:43

glad I could be of help though

oly13:01:21

so wonder if someone can help, I have logback installed with tools.logging but it seems like tools.logging is not picking up logback if i run (log-impl/find-factory) I can see it lists org.slf4j.helpers.NOPLoggerFactory which seems wrong to me, but I don't get how I change them / it decides to use that (I am not familiar with java logging). so perhaps I am missing something I have logback.xml in my resources folder with a basic config so whats my next step ?

manutter5114:01:41

Are you passing in -Dlogback.configurationFile=resources/logback.xml as a JVM option when you run your app?

oly14:01:32

nope, I got the impression it picked that up automatically I will give it a try

p-himik14:01:36

In my projects, I definitely don't specify it explicitly anywhere - logback.xml is just picked up from the classpath.

oly14:01:30

yeah I just read that is the case and the other option is if you want to override for say prod, not sure how i can confirm if its even picking up the file

oly14:01:21

I do get this when I launch SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder". which I have read should go when you import logback-classic into your dependency list

p-himik14:01:31

Just to make sure - when you run ( "logback.xml"), do you get some URL or nil?

oly14:01:40

yes I do get a file back, that is now going at the start of my app with a prn so I know which file its loading 🙂

oly14:01:42

I also notice that when launching my app I get this,

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See  for further details.
"file:/var/www/beanbag/workspace/projects/office-api/resources/logback.xml"
MLog initialization issue: slf4j found no binding or threatened to use its (dangerously silent) NOPLogger. We consider the slf4j library not found.
Jan 19, 2022 2:23:00 PM com.mchange.v2.log.MLog 
INFO: MLog clients using java 1.4+ standard logging.
Jan 19, 2022 2:23:00 PM com.mchange.v2.c3p0.C3P0Registry 
INFO: Initializing c3p0-0.9.5.4 [built 23-March-2019 23:00:48 -0700; debug? true; trace: 10]
after that last info all logging stops

oly14:01:20

I import tools.logging into my app should i also be importing logback even if I am not using it directly ?

manutter5114:01:13

You mean like via a (:import ...) clause in a namespace declaration? I don’t think so.

manutter5114:01:10

I wonder if there’s a typo somewhere in your logback.xml that is blowing up the xml parser and resulting in the “Failed to load class” error?

oly14:01:33

yeah, I guess the bit that bothers me is the magic part where its supposed to just use logback some how seems the magic is failing somewhere 😕

lukasz14:01:12

What's your dependency list? Usually logback-classic + c.t.logging + logback.xml in the classpath does the job. In some cases you need to add org.slf4j/slf4j-api

manutter5114:01:38

Maybe you could use clojure.data.xml to slurp and parse the logback.xml file, just to make sure it’s not blowing up during the parsing.

lassemaatta14:01:00

(related article giving an overview just in case you haven't seen it: https://lambdaisland.com/blog/2020-06-12-logging-in-clojure-making-sense-of-the-mess)

manutter5114:01:13

Yeah, Java logging is kind of a mess, and lucky us, we get to inherit the mess. 😕

oly14:01:21

I have read that article and many others, and tried a few different logging libraries 😛

oly14:01:38

ch.qos.logback/logback-classic {:mvn/version "1.2.10"}
        org.slf4j/slf4j-api {:mvn/version "1.7.33"}
        org.slf4j/jul-to-slf4j {:mvn/version "1.7.33"}
        org.slf4j/jcl-over-slf4j {:mvn/version "1.7.33"}
        org.slf4j/log4j-over-slf4j {:mvn/version "1.7.33"}
        org.slf4j/osgi-over-slf4j {:mvn/version "1.7.33"}
I did not have osgi so added that

oly14:01:13

I will try reading the xml file, I will be annoyed if it just ignores an invalid xml file with out some kind of warning though

p-himik14:01:16

Your logging output above shows that some thing called MLog is being used. I don't know for sure but my hypothesis is that it's being picked up instead of Logback. I'd track that dependency and try to exclude it.

oly14:01:17

clj -Stree | grep "mlog" does not find anything just trying the load the xml idea

p-himik14:01:27

Are you sure that when you're getting that log output with MLog you are not using some aliases that might affect the classpath?

p-himik14:01:06

Also, don't grep mlog - the dependency itself might very well have a different name.

p-himik14:01:27

The fully qualified class name that's being mentioned is com.mchange.v2.log.MLog. So, try using (io/resource "com/mchange/v2/log/MLog.class").

lassemaatta14:01:50

Perhaps it’s bundled into c3p0? :thinking_face:

oly14:01:16

yeah https://stackoverflow.com/questions/5686619/turning-off-logging-for-hibernate-c3p0/5698667 it mentions logback in the answer but does not have a full example

oly15:01:43

I get this back when dumping the mlog resource

file:/home/oly/.m2/repository/com/mchange/mchange-commons-java/0.2.15/mchange-commons-java-0.2.15.jar!/com/mchange/v2/log/MLog.class

lassemaatta15:01:43

at least the documentation for c3p0 claims compatibility with slf4j & logback (https://www.mchange.com/projects/c3p0/#configuring_logging). I'd suggest looking closely at the dependency tree and verifying you see the correct version of logback-classic there

oly15:01:42

yeah I just modified mlog config intrestingly it gave me this error

oly15:01:46

com.mchange.v2.log.MLogInitializationException: slf4j found no binding or threatened to use its (dangerously silent) NOPLogger. We consider the slf4j library not found

oly15:01:39

my understanding is logback should be providing the compatibility for slf4j

lassemaatta15:01:01

yeah, as long as the versions of slf4j and logback are compatible

lassemaatta15:01:52

> Note that slf4j-api versions 2.0.x and later use the https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html mechanism. Backends such as logback 1.3 and later which target slf4j-api 2.x, do not ship with `org.slf4j.impl.StaticLoggerBinder`.

lassemaatta15:01:48

e.g. ^^ that might be a problem if your project somehow loads a different newer version of logback rather than the one you specified above. But this is just a hypothetical guess

oly15:01:56

I just cleared my .cpcache folder and it may be fixed 😕

oly15:01:22

I was not getting these before INFO in ch.qos.logback.classic.joran.action.RootLoggerAction - Setting level of ROOT logger to ERROR

oly15:01:32

not sure at what point this got fixed now

oly15:01:34

I guess this is a case of a removed dependancy still in the cache folder perhaps

oly15:01:00

anyway thanks for all the help guys I think its solved now, the app even launches with these messages at the start now

15:14:23,706 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback-test.xml]
15:14:23,706 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Found resource [logback.xml] at [file:/var/www/beanbag/workspace/projects/office-api/resources/logback.xml]

👍 2
manutter5114:01:19

Has anyone here ever seen an actual bug that would have been prevented (or was fixed) by declaring a function with defn- instead of defn?

manutter5114:01:14

My theory is that defn- is a holdover from OOP, and adds no practical value to the art of functional programming.

javahippie14:01:09

I’m mostly using it to mark things in my ns which should not be relied upon, mainly “don’t use that, it might change” (e.g. not a part of the API)

☝️ 1
1
Jon Boone14:01:47

Can you elaborate on the notion of it being a “holdover from OOP”?

emccue14:01:21

OOP generally emphasizes private stuff - i will disagree though. It still makes sense to have a public and private api for things

emccue14:01:24

(defn- length-helper [collection n]
  (if (empty? collection)
    n
    (recur (rest collection) (inc n))))

(defn length [collection]
  (length-helper collection 0))

emccue14:01:04

basic example - but its pretty easy to have functions in a namespace that dont make sense to expose and are really implementation details

manutter5114:01:21

Ok, I’ll buy the use case where you’re publishing an API and you want to distinguish between what’s “official API” and what’s “internal implementation detail subject to change.” My beef is with seeing it used in standalone proprietary app code where it complicates things without adding any value.

javahippie14:01:34

Our standalone proprietary app code also has some kind of internal API. I liked @U3JH98J4Rs example. If we have a ns with a lot of such functions which are only used internally to clean things up, the devs might be happy to not have them provided by the code completion tools. But I get that this is more opinion based

dpsutton14:01:12

just checked, and there are 114 private vars in clojure.core. Thought it was an interesting datapoint

octahedrion14:01:54

clojure.core is the exception that proves the rule -- that there's no point in defn- for 99% of cases. Instead use namespaces to separate API from implementation and make everything public

dpsutton15:01:29

clojure.set has a single private function. Would seem strange for there to be a clojure.set.impl with a single function and only one namespace that would call it. That seems far worse than a little metadata marking something private

Jon Boone15:01:59

I first learned about namespaces (packages) in CL. There is no access control enforcement mechanism there, so I don't consider them as being OOP related at all.

lread15:01:19

I find defn- useful. Like @U0N9SJHCH mentioned, it helps to distinguish internal from external. For maintenance, it also helps me to more easily find and obliterate unused code, although clojure-lsp now helps with finding unused public things too. There is also the ^:no-doc convention to mark non :private vars and nses as internal. I use this too.

dharrigan15:01:27

I go with the meta data route, i.e., ^:private

octahedrion15:01:42

I like to signal intent with

(alter-meta! #'your-var assoc :private false)
I hope you documented why that function was private

😄 1
octahedrion15:01:36

@U025L93BC1M Smalltalk uses the same convention - you can categorize methods as private purely to the inform the user of the class, no enforcement

octahedrion16:01:12

@U11BV7MTK clojure.core does many things which mortal programmers don't need to do, either for technical or historical reasons

Joshua Suskalo16:01:58

I think calling defn- a holdover from oop is like calling spec a holdover from static typing. Its usecase is to allow you to implement your api, the namespace's public functions, in a way that holds a stable interface even as you improve the internals both in style, readability, performance, and other ways. Otherwise I think yes using defn over defn- does cause "bugs" in a sense, where a library dev either has to keep old helper functions around etc for fear of breaking callers, or they do break some small subset of callers by updating their internals. Either way is not ideal when the callers don't have a clear signal that it's internals subject to change.

Joshua Suskalo16:01:48

You could put internals in an impl namespace, but I think that only makes sense when you have a lot of stuff to put in there or for tests and similar. A lot of smaller helpers get tested implicitly by testing the public api though, so in many cases it's just nicer to stick the helper next to where it's used, and in those cases defn- is appropriate.

octahedrion11:01:35

another way is to make everything implicitly (public) impl and include an .api namespace for the api

ghadi14:01:53

it's low value in the grand scheme of things

Nom Nom Mousse14:01:06

I use it to signal intent 🙂

Cora (she/her)15:01:44

I like ^:private instead, it signals intent with a bullhorn

Ben Sless15:01:02

If I don't want consumers to use it, it goes in an impl namespace, otherwise I'm just making work for myself if I want to test it, too

👍 6
quoll15:01:46

Luckily, it’s not THAT much work.

=> (clojure.core/spread [:a :b [:c]])
Syntax error (IllegalStateException) compiling clojure.core/spread at (REPL:1:1).
var: #'clojure.core/spread is not public
=> (#'clojure.core/spread [:a :b [:c]])
(:a :b :c)

hanDerPeder18:01:42

Why does this work? In my mind it goes symbol -> var -> value. This passes var instead of value, but that has not be looked up as some point, right? Or is #'foo a different var than the var which foo resolves to?

hanDerPeder18:01:26

after thinking, it’s probably because the meta check happens when resolving the symbol -> var, right? when you access the var directly this is obviously skipped.

quoll19:01:37

I think Alex will have a better answer, but I’ll present what I know. clojure.core/spread is a symbol. When eval-ed then it is looked for in the namespace. This lookup will find a Var object, but at this point it will see that the object referenced by the Var is marked as private, and an exception will be thrown. This happened in the .resolve part of the evaluation, and before it looks at the Var. If it weren’t private, then the var returned would contain the IFn and the eval usually continues by doing that dereference. #'clojure.core/spread is a reader macro that is equivalent to (var clojure.core/spread). Evaluating this creates the var object, and looks it up in the current namespace, but importantly, it uses an internal method called .lookupVar and does not use the .resolve method. Both of these methods lookup the value of the var from the namespace, but only .resolve checks the privacy flag.

hanDerPeder20:01:10

thanks, think that sort of matches where I ended up when thinking it through. always nice 🙂 also interesting to look at the compiler code. never dived in before but this seems fairly approachable. seems there’s also a path to accessing a private var by calling (clojure.lang.Compiler/resolveIn ns sym true) . Neat!

quoll21:01:14

Just don’t write production code that way 😜

borkdude16:01:31

Another way to really get inaccessible helper functions (well, you get still get to them if you're a hacker):

(let [private-fn (fn ...)]
  (defn public-fn-1 [] (private-fn ...))
  (defn public-fn-2 [] (private-fn ...)))
but why....? Just don't ;).

Ben Sless16:01:56

Since they're all reachable, I default to the "polite agreement" solution

Nom Nom Mousse16:01:32

I am used to updating functions live when the app is running and having them change behavior. For some reason, updating functions f added to tap with (add-tap f) does not seem to work. I need to restart the app. Is there a workaround?

p-himik16:01:53

Use (add-tap #'f).

Nom Nom Mousse16:01:02

I assumed it had something to do with pointers. Will read it :thumbsup:

Alex Miller (Clojure team)16:01:57

indeed it does! #' is a var, which is a pointer to a function

🙏 1
Joshua Suskalo17:01:31

As a short description, when you write (add-tap f) what's happening is it's looking up the var f and fetching its current value, which is a function, and passing that value to add-tap. Then when you update the function what's happening is you're making a brand new function and assigning that value back to the var f, but this doesn't update the tap because it doesn't look up the var again, it just uses the returned value. When you pass #'f then you're passing the var itself to add-tap, which only works because vars implement the IFn interface by looking up the value in the var and calling it as a function. If that weren't a part of vars, this wouldn't work.

🧠 1
Joshua Suskalo17:01:06

So every time tap calls your "function" it does a fresh lookup of the var.

🙏 1
vemv19:01:00

Fun question perhaps, even if I think the answer will be no: Can I add many jars to the classpath as one entry, without making an uberjar?

Joshua Suskalo19:01:16

you could add a single jar that has nothing but a meta-inf that includes a bunch of other relative paths to other jars to add

vemv19:01:52

what's the gist of this solution? I took a look at https://s3.amazonaws.com/athena-downloads/drivers/JDBC/SimbaAthenaJDBC-2.0.27.1000/AthenaJDBC41_2.0.27.1000.jar but found various stuff, not sure of what is the one using the technique you refer to

vemv19:01:01

(maybe Athena has nothing to do and I was fooled by my googling)

Joshua Suskalo14:01:13

I'd observed it on the redis jar @U45T93RA6

vemv19:01:04

which one is it?

Joshua Suskalo19:01:49

I know this is how e.g. the amazon redis jdbc driver jars declare a dependency on log4j

vemv19:01:08

That... sounds beautiful :) will give it a spin

Alex Miller (Clojure team)19:01:23

you can use a Class-Path manifest attribute (was designed for applets)

Alex Miller (Clojure team)19:01:51

this is sufficiently obscure that your colleagues may hate you for it though :)

clojure-spin 2
🍻 2
coltnz19:01:50

Classpath entries support as wildcard (but not .jar) e.g. -cp lib\*

🙌 1
jjttjj21:01:58

I have a pattern where I'm building up a map by chaining some initial input though several handler functions. I have multiple function chains I need to execute on vaguely similar input data with some common steps between them. Any one of these functions can result in some error-like/termination condition, at which point I want the chain to halt and log the built up map at this point. The map being built up is pretty huge and not really ideal to have printed (but small enough where I want to log it entirely). In this case, what do you prefer (or what factors would lead you to prefer one of these): 1. throwing exceptions in the handler functions with ex-info and include as the ex-data the built up map at that point. Then wrap the handler chain in a try/catch, and use the ex-data to log the data. This seems the most normal and obvious, I only pause because there end up being lots of throw statements large data structures included in the ex-data. Edit: and I'm not sure how I feel about depending on the the existence of a key in the ex-data in the the thing that invokes the function chain. Is that normal? 2. Use something like https://github.com/cognitect-labs/anomalies or really just a keyword indicating an error that any of the functions can return in the map. If you use this, how do you deal with early termination in a large chain of functions? I have tried out a custom threading macro for this which checks for the error key at each step but this isn't particularly satisfying. 3. Use some library which implements the interceptor pattern? There are some lightweight ones, but this feels like overkill since the only feature i need is early termination (and not the whole enter/exit lifecycle) 4. anything else?

Ben Sless06:01:47

How about a cps transform with success and failure continuations?

Ben Sless06:01:00

Like async ring middleware

hiredman21:01:06

I've built a few data processing pipelines that sound like that, and usually end up with what is basically an either monad

hiredman21:01:08

where the value is either some bit of data that the pipeline operating on, or a value representing an error

hiredman21:01:36

and then each step of the pipeline operates on the data or passes through errors unchanged

hiredman21:01:18

I've never used a library or anything for that, usually just define a few helper functions

(defn just [x] {:data x})
(defn error [x] {:error x})
(defn app [f x] (if (:error x) x (f (:data x))))

jjttjj21:01:38

Yeah have something roughly similar just not quote sure how I feel about it

hiredman21:01:42

works very well with reduce based things (like transduce)

Max23:01:38

@jjttjj I wrote up a little thing recently that might work for you, it decomplects mapping thrown exceptions to return values from early return points: https://clojurians.slack.com/archives/C03RZGPG3/p1642025029110800