Fork me on GitHub
#beginners
<
2024-04-18
>
Jim Newton07:04:15

is there a suffix to put on clojure scratch files to indicate that they contain clojure code, but should not be loaded as part of the project? In scala I can use .scala to mean a file that the compiler needs to consider when compiling the project, and .sc which is a file the compiler should ignore unless explicitly loaded.

Jim Newton07:04:22

I think in clojure, a file won’t get loaded anyway unless explicitly required. so such a suffix is perhaps not technically needed. however, it might be nice to distinguishing them to a human pursuing the project.

daveliepmann08:04:57

The idiom I've seen for this is to put scratch files in a separate repl_sessions directory.

1
jpmonettas11:04:28

yeah, I put them on a dev-src folder, and then have a :dev alias in deps.edn with :extra-paths ["dev-src"]

👍 1
3starblaze08:04:59

How does work? Is it just something that searches the files in the classpath? So anything that is in :paths in deps.edn is discoverable by ? If so, how are the files searched and what would happen if classpath had 2 files with the same name?

3starblaze08:04:33

For the context, I am making a shared edn file that defines build ids and which are meant to be consumed by bb, shadow and backend. I thought that making that edn file as a resource is the way to go and I managed to obtain the file via resource function although I am not sure how it works.

teodorlu08:04:19

I think you’re on the right track, and that this is a good use case for io/resource. Collisions are often avoided with namespaces. If I’m making an app

eu.teod.weeknotes-notes
I’d put an image for that app in a path like
resources/eu/teod/weeknotes-notes/book.png

phill10:04:45

The Clojure documentation (https://clojure.github.io/clojure/clojure.java.io-api.html#clojure.java.io/resource) has a link to "Source" (https://github.com/clojure/clojure/blob/ee1b606ad066ac8df2efd4a6b8d0d365c206f5bf/src/clj/clojure/java/io.clj#L446) which shows that it's merely a call to Java's .getResource on the context class loader, https://docs.oracle.com/javase/8/docs/api/java/lang/ClassLoader.html#getResource-java.lang.String-. Bottom line, there might not be guarantees about how the case of multiple matching resources would be handled. Better to avoid that case!

Alex Miller (Clojure team)11:04:37

Answering the original questions, yes it searches the classpath and can find anything in your deps. If multiple, it will find the first one on your classpath. Classpath order for deps.edn is defined here: https://clojure.org/reference/deps_edn#deps

👍 1
til 1
Alex Miller (Clojure team)11:04:36

Generally, just as with namespaces, you should place resources at a path you “control” via reverse dns, trademark, etc

👍 1
3starblaze18:04:10

Ah, now I get it, I should treat my resources the same way I do with Clojure source files via namespaces. Thank you for the detailed answers!

yes 1
❤️ 1
Duminda09:04:27

What's the best way to cancel some running futures? I'm having issues with below. Simplified code:

(def running-workers (atom []))

(defn cancel-all []
 (doseq [worker @running-workers]
  (future-cancel worker))
 (reset! running-workers []))

(defn run []
 (cancel-all)
 (let [fs (for [i work]
           (future
            (let [vals (get-vals i)
                  _ (doseq [val vals]
                     (try
                      ;... fetch from db and do some transforms and write to files
                      (catch Exception e
                       (println "Error: " e))))])))]
  (swap! running-workers into fs)
  (doall fs)))

(comment

 (run)
 (cancel-all)

)
cancel-all doesn't properly cancel all threads. I'm guessing this is because the cancel interrupt is caught by the catch? Is there a better way/lib to do this?

Duminda10:04:35

Used :while in doseq and a flag

Ed11:04:04

Cancelling futures isn't all that straightforward. There are lots of elements to stopping a thread from running. future-cancel calls this: https://docs.oracle.com/en/java/javase/22/docs/api/java.base/java/util/concurrent/Future.html#cancel(boolean) which may end up calling https://docs.oracle.com/en/java/javase/22/docs/api/java.base/java/lang/Thread.html#interrupt() and depending on what's happening at the time that is called, it might be your responsibility to check the interrupted status on the thread because it's not guaranteed to throw an InturruptedException, for example. If you want total control over how the process stops, it may be best to implement some kind of mechanism yourself, like an atom you can check in the loop, or other control latch.

👍 1
jpmonettas11:04:05

Yeah what I always do is to do looping (loop, while, etc) and check with (while (not (.isInterrupted (Thread/currentThread)) ...)

👆 1
jpmonettas11:04:41

for your case, you can do like :

(future
  (let [vals (get-vals i)]
    (doseq [val vals
            :while (not (.isInterrupted (Thread/currentThread)))])
    (try
      ;;... fetch from db and do some transforms and write to files
      (catch Exception e
        (println "Error: " e)))))

👍 1
Ed11:04:45

One issue with relying on the interrupted state on a thread is that, depending on what you're doing, you may want the ;... fetch from db and do some transforms and write to files to complete all or none of it's work when being cancelled.

👍 1
Duminda12:04:11

Used @U0739PUFQ suggestion. Is it wrong to think that this is a common use case?

jpmonettas12:04:04

it is, but there is no general solution. The Thread class used to have thread.stop() but it has been deprecated. So the only safe way of stopping threads on the JVM is that the code adds check points to see if it should continue. See here for more details https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/doc-files/threadPrimitiveDeprecation.html

👍 1
Nim Sadeh14:04:04

Is there a way to convert any exception to something that conforms to IExceptionInfo so that ex-data can log it out nicely/consistently? I still can't figure out how to log exceptions in Clojure, I still occasionally get nil in my logs because something failed deep in a Java dependency 😞

dpsutton14:04:52

how are you logging exceptions? I’ve found that (log/error e "error doing foo") can get you pretty far

Nim Sadeh14:04:11

I use mulog, so it looks something like this:

(mu/log ::exception-during-task
        :request-id rid
        :message (.getMessage e)
        :data (ex-data e)
)

dpsutton14:04:33

(try
  (something)
  (catch Exception x
    (μ/log ::actionX :exception x :status :failed)))
are you willing to just throw the whole exception in there like this example?

dpsutton14:04:16

user=> (μ/log (Exception. "bad thing happened"))
nil
user=> {:mulog/trace-id #mulog/flake "4wS_ubSjkxAliogCJddxJAkILQ7cURd2", :mulog/timestamp 1713450240939, :mulog/event-name #error {  :cause "bad thing happened"  :via  [{:type java.lang.Exception    :message "bad thing happened"    :at [user$eval794 invokeStatic "NO_SOURCE_FILE" 1]}]  :trace  [[user$eval794 invokeStatic "NO_SOURCE_FILE" 1]   [user$eval794 invoke "NO_SOURCE_FILE" 1]   [clojure.lang.Compiler eval "Compiler.java" 7194]   [clojure.lang.Compiler eval "Compiler.java" 7149]   [clojure.core$eval invokeStatic "core.clj" 3216]   [clojure.core$eval invoke "core.clj" 3212]   [clojure.main$repl$read_eval_print__9206$fn__9209 invoke "main.clj" 437]   [clojure.main$repl$read_eval_print__9206 invoke "main.clj" 437]   [clojure.main$repl$fn__9215 invoke "main.clj" 458]   [clojure.main$repl invokeStatic "main.clj" 458]   [clojure.main$repl_opt invokeStatic "main.clj" 522]   [clojure.main$main invokeStatic "main.clj" 667]   [clojure.main$main doInvoke "main.clj" 616]   [clojure.lang.RestFn invoke "RestFn.java" 397]   [clojure.lang.AFn applyToHelper "AFn.java" 152]   [clojure.lang.RestFn applyTo "RestFn.java" 132]   [clojure.lang.Var applyTo "Var.java" 705]   [clojure.main main "main.java" 40]]}, :mulog/namespace "user"}

Nim Sadeh14:04:42

I prefer not to, that's why I like using ex-data

Ed14:04:40

ex-info accepts at third arg that can be any Exception as a cause. In Java land this is for chaining exceptions together, does this log any differently?

Noah Bogart14:04:57

if the exception isn't an ExInfo, then ex-data will be nil anyway, right? what is the desired outcome here?

Nim Sadeh14:04:36

Ed, I think that helps a lot - you're suggesting that I basically use ex-info on exceptions that don't conform to force them to conform? That's exactly what I was looking for. Noah - ideally looking to make every exception an ExInfo so it can log nicely

Ed15:04:13

Any exception can have a cause - it's an optional field on the Exception class. Using ex-info is an easy way to wrap exceptions so you can attach clojure data. But you're never going to be able to do that for all cases. You can get a stack overflow or an out of memory error at any point, for example. So it kinda depends on what your goal is. If you want to tag exceptions at some point in your program and log them in some generic way (like in middleware or something) then that'll help you tag them with useful data. But if you just want to wrap exceptions inside the logging function, you're not going to have any meaningful data to attach, like Noah says, right? So maybe you just want something like

(mu/log ::exception-during-task
          :request-id rid
          :message (.getMessage e)
          :data (if (instance? IExceptionInfo e) (ex-data e) :no-ex-data))
if you don't want to log nil?

🤝 1
🙌 1
Eric Chua21:04:00

For ratio e.g. (/ 8 3) will return 8/3 1. What is the use case of ratio? Like when should I really use it? I googled it, and it says ratio can give accuracy compare to float. But still when should I even use ratio at the first place? 2. What if I am calculating a long formula and expecting an integer or float, but somehow it returns a ratio? Won't this cause my program crash or something? Assume some variables to the formula will either result in a integer/floating number and sometimes a ratio?

Alex Miller (Clojure team)21:04:13

if you are doing integer math then division will create ratios, and be exact. if you want decimals, use decimals. (/ 8.0 3.0) Clojure ratios are Java Numbers, and can be coerced to any other numeric type if needed. Nothing will crash.

👌 3
henrik07:04:29

(def a
  (-> (/ 8 3)
    (* 5)
    (+ 1)
    (* 7)
    (/ 9)))
;; => 301/27
(double a)
;; => 11.14814814814815


(def b
  (-> (/ 8.0 3.0)
    (* 5.0)
    (+ 1.0)
    (* 7.0)
    (/ 9.0)))
;; => 11.148148148148147

👍 2
Eric Chua12:04:00

I see, so the decimal one is more accurate as it has a scale of 15. So I assume when I divide, best I use decimal. So the ratio seems redundant and not recommended to use?

henrik12:04:07

I’m not sure that this follows. I think ratio is more accurate as you delay rounding errors until the end of the calculation. I think it ends up being 147 due to accumulated inaccuracies.

henrik12:04:47

I’ve never been in a situation where that decimal would matter OTOH.