clojure 2025-08-21

Somewhat related I guess: is there an easy way to enable *warn-on-reflection* (and set *unchecked-math* to :warn-on-boxed) at the "project" level from deps.edn, akin to Leiningen's :global-vars?

I've been looking for the same long ago, but the answer is no. You cannot do it in an easy way.

Use alter bar root to set warn on reflection to true in user.clj, this works and is pretty easy although the core team is thinking about making it even easier

Sorry for spelling, phone kbd …

This trick didn't work for me, I mean the user namespace. If a project has its own user.clj file, it substitutes the global one. Maybe it's me who is wrong but I didn't make it work

Global one? I’ve never used a global user.clj

In lein, it's possible to declare global-vars like warn-on-reflection on a global level: such that ANY (literally ANY!) lein project will have these settings by default when run. Namely, it's a file like this:

~/.lein/profiles.clj

{:user {:global-vars {*warn-on-reflection* true}}}
I was trying to have the same global user.clj file in deps, but it turned out local user.clj files override it

πŸ‘ 1

And that's a real issue. I noticed people who work with deps usually know nothing about reflections. There is no an easy way to enable reflection warnings globally, but setting them explicitly in each namespace is annoying.

πŸ‘ 1

I'm thinking it's by project rather than by namespace. Am I wrong?

The *warn-on-reflection* setting, when set! manully, affects only the current namespace.

[*ns* *warn-on-reflection*]
;;=> [#namespace[user] false]

(set! *warn-on-reflection* true)
;;=> true

[*ns* *warn-on-reflection*]
;;=> [#namespace[user] true]

(ns user2)
;;=> nil

[*ns* *warn-on-reflection*]
;;=> [#namespace[user2] true]
In my quick tests, the effect of set! is global

it's per loaded file

which typically comes down to "per namespace"

https://clojure.org/reference/vars#set I didn't know that. Is there any documentation about it? The documentation I found states that global variables change thread-locally.

What happens when clojure loads a file using clojure.lang.Compiler/load is that the thread-local binding is reset to the original one

you can look at that source to understand

your understanding of set! is correct, this resetting per file (or more specifically load ) is specific to a couple of vars though

user.clj being automatically loaded always seemed too magical to me. In leiningen, I have this in my (user-global) profiles.clj:

:repl {:pedantic? :warn
        :global-vars {*warn-on-reflection* true
                      *unchecked-math* :warn-on-boxed}}
so the warnings only happen on lein repl but do not affect lein uberjar or lein run. I've also typically added a dev/repl.clj file (`repl` ns) that is only loaded on lein repl, and is the default namespace there. It looks like the only option for something similar through clj/`deps.edn` is to still have a dev folder that only gets loaded with my REPL alias, but now the namespace has to be user.clj if I want it loaded automatically, and the global vars should be set in that file using alter-var-root. Am I getting that right? Also, does that mean I don't have a way to set the global vars globally for my user, instead of per project?

Thanks borkdude! This point really needs to be clearly documented... That would mean the graal-docs documentation (which I wrote) is incorrect. Apparently, you need to add (set! *warn-on-reflection* true) to the top of every source file.

https://clojure.org/reference/repl_and_main#_loading_of_user_clj It may seem like user.clj is being loaded magically, but that's actually documented behavior.

your docs aren't incorrect, the user.clj trick works

hum, meaning thread-local bindings are supposed to be reset, but *warn-on-reflection* isn't resetβ€”so essentially, declaring it once in user.clj makes it effective for all namespaces? Does that sound right?

in user.clj the root binding is changed. this is what's used in Compiler/load to reset it to for every load call

OK, I see. thanks!

Documented magic is still magic πŸ™‚

@borkdude oops, Should I use (alter-var-root #'*warn-on-reflection* (constantly true)) instead of (set! *warn-on-reflection* true) to change root binding?

this part:

(ns user)

(alter-var-root #'*warn-on-reflection* (constantly true))
changes the root binding

which is part of your documentation change

I forgot what I wrote earlier lol OK, I finally completely understand it now

FYI: after nREPL 1.4.0, it has a global config https://clojurians.slack.com/archives/C06MAR553/p1756827753587169 so, you don't need to configure *warn-on-reflection* for each project.

Thank you!

πŸ‘ 1

yeah, that's great news. Worth adding this file to one's dotfiles

I need my clojure webserver to trigger an action roughly once a day, not at any time in particular. Is this an ok rough plan, or should i be library hunting?

(defonce scheduled-counter
    (future
      (loop [n 0]
        (Thread/sleep 2000)
        (recur (reset! counter (inc n))))))

@potetm i also couldn't understand if using core async would help here, so i opted not to, i dont see how backpressure and a queue would matter.

@ed121 do you think quartz makes more sense then the Chime library? Thats what i started investigating today. I dont' care much about skew, it just needs to roughly run every other day. And i don't care about the schedule if the server goes down, so no need to hook this to a persistent storage.

@didibus the counter atom was in place of the actual state i would be storing, which will be api responses.

How do i use jarohen/chime's https://github.com/jarohen/chime/blob/9f2f87ebe65e5bbcd0781712489f407df5851826/src/chime/core.clj#L57 such that each "chime" runs in the background? I'm i supposed to be doing something with the default-thread-factory? if not, whats the purpose of a thread factory here? the code for that is confusing to me as i don't reify things very often (let say never) and it seems to be creating a thread and just giving it a name... Do i just run the chime-at function in a future? e.g (chime-at times #(future ...))?

If you don't care about drift, accuracy, and don't need it lined up with a calendar, I think what you had is fine.

πŸ‘ 1

awesome, thanks for the feedback!

You might just want to add some exception handling, since anything that throws in future mostly gets swallowed and kills the thread.

πŸ‘ 1

in the morning, ill probably decide i should use a different trigger then the passage of time, just to avoid this awkwardness...

you could also use async alt! to wait on a timeout or a manual trigger channel

πŸ‘€ 1

whats your intuition on why that would be better? For context, inside the loop above, ill need to call an api and fetch some data we want to cache.

i might fall asleep before i see more answers... πŸ’€

aren't all futures sharing a threadpool , so you don't want too many forever running threads there

also, need robust error handling an intermittent failure doesn't stop your background task

using core.async might or might not use fewer resources, it’s tough to say. older versions used a fixed thread pool, in which case it would definitely be a larger footprint. core.async is more efficient when you need a large number of processes. if you wanna use as few resources as possible, you can consider using ScheduledExexutorService. if you use Exeutors/newScheduledThreadPool with a core pool size of zero, it should release the thread when you’re not using it. that said, what you have is probably fine for the most part as long as you try/catch around your work.

fwiw, most likely just a (.start (Thread. (fn [] (loop ...)))) will work fine

you could use https://github.com/quartz-scheduler/quartz/https://github.com/michaelklishin/quartzite. It'll at least give you the ability to retry on failure conditions

Future thread is unbounded. Don't think there's an issue in using it.

Just need to be very careful about errors. Probably catch everything inside the loop and just log or save that information somewhere.

Why do you have a counter though?

Oh, the counter is what you need to increment once a day πŸ˜„

Do you intend to have Thread/sleep sleep for like 24h ? I'd also read up on that for wtv OS you are using.

or Quartzite like Ed suggested

Can anyone remind me please, how to install a project into a local maven directory? E.g. what exactly lein install does, but using cli deps

if you don't have a build.clj you could use neil add build if you have #babashka-neil installed it will generate a build.clj file in your project with an install function, so then it would just be clj -T:build install

I'm getting weird output:

clj -X:deps mvn-pom ;; this is ok

clj -X:deps mvn-install :lib  :classifier aaa :version "123.123"
it gives
Installing nil
Execution error (ClassCastException) at clojure.tools.deps.cli.api/mvn-install (api.clj:394).
class clojure.lang.Symbol cannot be cast to class java.lang.String (clojure.lang.Symbol is in unnamed module of loader 'app'; java.lang.String is in module java.base of loader 'bootstrap')

Full report at:
/var/folders/j7/0xr4lj9s0nv4dy8y4m0wzzcw0000gp/T/clojure-5843429173408538261.e

'"123.123"'

or maybe :classifier '"aaa"'

you need to convert something to an explicit string. everything is parsed as EDN

tried this:

clj -X:deps mvn-install :lib '' :classifier '"aaa"' :version '"123.123"'
and it still says "Installing nil" and the same error

Perhaps same for :lib?

no :lib should be a symbol

oh -X:deps has mvn-install, I did not know

And that's what we have: three Clojure people are guessing. I cannot remember a single case when lein install had failed for the whole 10 years

let me try it locally

$ clj -X:deps mvn-install
Installing nil
Execution error (FileNotFoundException) at clojure.tools.deps.cli.api/read-pom-in-jar (api.clj:324).
Jar file not found:

sorry for sarcasm but really, why not checking the input parameters with spec?

I think you're going to be better off with a build.clj in place and with clj -T:build install. at least I know that works

you're going to need it anyway

I mean, how else did you get the jar file?

why not building a jar file, when it's missing? or at least say "the jar file is missing"? And somehow, lein checks for this case: lein install produces a jar when it's missing

that's what you're going to have when using a build.clj

rm -rf target
lein install

Created /Users/.../work/taggie/target/taggie-0.1.1-SNAPSHOT.jar
Wrote /Users/.../work/taggie/pom.xml
Installed jar and pom into local repo.
and this is it

Would you like to have an example build.clj that neil add build would generate for you? I can copy-paste it here

Btw, you are not alone in your experience of "easy lein vs decomplected tools.deps/deps.edn". It's what many people experience, I don't want to downplay that.

yes, pointing me to such a script would be nice

(ns build
  (:require [clojure.tools.build.api :as b]
            [clojure.edn :as edn]))

(def project (-> (edn/read-string (slurp "deps.edn"))
                 :aliases :neil :project))
(def lib (or (:name project) 'my/lib1))

;; use neil project set version 1.2.0 to update the version in deps.edn

(def version (or (:version project)
                 "1.2.0"))
(def class-dir "target/classes")
(def basis (b/create-basis {:project "deps.edn"}))
(def uber-file (format "target/%s-%s-standalone.jar" (name lib) version))
(def jar-file (format "target/%s-%s.jar" (name lib) version))

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

(defn jar [_]
  (b/write-pom {:class-dir class-dir
                :lib lib
                :version version
                :basis basis
                :src-dirs ["src"]})
  (b/copy-dir {:src-dirs ["src" "resources"]
               :target-dir class-dir})
  (b/jar {:class-dir class-dir
          :jar-file jar-file}))

(defn install [_]
  (jar {})
  (b/install {:basis basis
              :lib lib
              :version version
              :jar-file jar-file
              :class-dir class-dir}))

(defn uber [_]
  (clean nil)
  (b/copy-dir {:src-dirs ["src" "resources"]
               :target-dir class-dir})
  (b/compile-clj {:basis basis
                  :src-dirs ["src"]
                  :class-dir class-dir})
  (b/uber {:class-dir class-dir
           :uber-file uber-file
           :basis basis}))

(defn deploy [opts]
  (jar opts)
  ((requiring-resolve 'deps-deploy.deps-deploy/deploy)
    (merge {:installer :remote
                       :artifact jar-file
                       :pom-file (b/pom-path {:lib lib :class-dir class-dir})}
                    opts))
  opts)

πŸ’― 1

you can copy paste this into your project and adapt the code accordingly

then call clj -T:build install. it's going to make the jar file and will install it locally

Here's also the corresponding deps.edn:

{:deps {}
 :aliases {:build {:deps {io.github.clojure/tools.build {:git/tag "v0.10.10"
                                                         :git/sha "deedd62"}
                          slipset/deps-deploy {:mvn/version "0.2.2"}}
                   :ns-default build}}}

Re. deps CLI - yeah, Sean had to write a whole article. :) https://clojurians.slack.com/archives/C8NUSGWG6/p1754792146304269

Both were generated with neil add build which is going to work in existing projects as well

Thank you! But I still have a strong feeling it doesn't have to be like this, really. Writing build scripts and calling a chain of commands VS one command look very, very strange

once you have this in place, it's just one command

In my career so far, looking at my colleagues over time and companies, I have noticed there are two general approaches to project management. Some people, somewhat rare, like to keep it extremely simple. A project is a pile of code with a list of dependencies, you compile it, you run it, and that's pretty much it. Leiningen works really well out-of-the-box for that approach. clj feels alien and overly complicated as the simplest things require massive efforts, and you never want the complicated things in the first place. Some people like to configure their builds. They want nested projects, configuration sharing, abstracted build steps, coordination between assets and code, etc. When using Leiningen, they spend a lot fo time hunting for plugins that do what they want, or need to develop their own, and plugin development is not exactly straightforward. Leiningen is a straightjacket they can't wait to get out of. clj is a real blessing for them as it lets them do everything they want in their build as "imperative" code directly; sure, the simple stuff requires a bit more involvement, but it's simple anyway, and now they can do all the other stuff. To some extent, the project influences which side you'll end up on, but my experience is it's mostly a personal approach question. Note that Leiningen is still around and nobody forces you to move off it, if it works for you.

πŸ‘ 2

Last when I had to do it, I just added a project.clj (with dependencies as in deps.edn) and ran lein install. (Not ideal, but gets you there without much overhead.)

if lein dependency resolution would switch to tools-deps I think it would be a greater tool which makes some things even easier, like using local deps or a more sane way of choosing "the newest" dep

πŸ‘πŸ½ 1

I know we have a few tools in the ecosystem for this, but is there at this point any "this is the best one" tool for generating documentation of namespaces/libraries? (as usual, I have a crackpot idea)

I think https://github.com/borkdude/quickdoc takes the cake for "gets the job done as quickly and simply as possible" - a markdown file displays well enough on GitHub for docs, and of course it could be used to generate HTML if that's needed.

1

> is there at this point any "this is the best one" tool for ... I'm sorry, this is a channel for clojure projects.

😁 3

+1 for quickdoc

quickdoc does give me an idea for how to proceed

It looks like it would be trivial to roll my own but in case one already exists, is there a Github Action for updating docs generated by quickdoc for a repo when new changes are merged in?

@emma.griffin I think it would be better to write a git commit hook for this maybe? since updating the docs would involve creating another commit in your repo

yeah maybe a pre-commit hook?

that's a good idea

my interest here is in making "linkable" documentation

so like you can have one web page with all the docs from all your libraries

this mandates something of a

 -
                \
                 ----> Intermediate format
javadoc --------/  |
                   |
 |
And then a
Intermediate format + IF + IF + IF -> 
flow

specifically structured folder of markdown files is not a bad place to start

especially since javadoc supports markdown directly now: maybe there can be some cross polination

(defn add
  "Adds two numbers together

@param x The first number
@param y The second number
@return the sum of x and y"
  [x y]
  (+ x y))

/// Adds two numbers together
/// 
/// @param x The first number
/// @param y The second number
/// @return the sum of x and y
int add(int x, int y) {
    return x + y;
}

absolutely cljdoc

For my library https://github.com/steffan-westcott/clj-otel I use AsciiDoc *.adoc files, which contain links to API docs hosted by cljdoc. The library source is hosted on GitHub. cljdoc and GitHub render AsciiDoc files, allowing them to be browsed on either platform. GitHub also supports Mermaid diagrams, although this feature isn't available on cljdoc.