clojure

Jim Newton 2026-05-26T09:08:55.054429Z

I’m getting ready to teach the first day of clojure to some students. I’m reviewing my understanding of how the clojure repl works as opposed to the repls in other lisps. Can someone help me understand why evaluating the list (1 (println 2)) fails without printing 2. My guess would have been that all the elements of the top level list would be evaluated BEFORE it discovers that 1 is not a function. So I’d have expected the side effect to occur before the exception.

Jim Newton 2026-05-26T09:09:42.559609Z

TL;dr. what does the REPL do when I try to evaluate a list?

Jim Newton 2026-05-26T09:10:55.946329Z

BTW, this behavior is not unique to clojure. elisp does basically the same thing.

Jim Newton 2026-05-26T09:11:33.649939Z

elisp also discovers that 1 is not a function before evaluating (println 2)

Jim Newton 2026-05-26T09:14:19.421449Z

my guess is that it first examines the first argument to decide whether it is a special form, a macro, a keyword, or several other possibilities. and if it is a symbol designating a known variable. and only in some of these cases does it proceed to evaluate the remaining arguments. e.g. we certainly would not want the println to be called in the cases that we have entered a top-level macro call.

Jim Newton 2026-05-26T09:16:55.420889Z

However, this does not exactly seem to correlate to the documentation description found here. https://clojure.org/reference/evaluation Which says

If the operator is not a special form or macro, the call is considered a function call. Both the operator and the operands (if any) are evaluated, from left to right. The result of the evaluation of the operator is cast to IFn (the interface representing Clojure functions), and invoke() is called on it, passing the evaluated arguments.

Jim Newton 2026-05-26T09:17:37.471099Z

This seems to imply that the arguments are evaluated before the first argument is case to IFn.

p-himik 2026-05-26T09:29:52.915699Z

The easiest way to inspect things like this is probably with clj-dava-decompiler:

user=> (add-lib 'com.clojure-goes-fast/clj-java-decompiler)
[com.clojure-goes-fast/clj-java-decompiler org.bitbucket.mstrobel/procyon-compilertools org.bitbucket.mstrobel/procyon-core]
user=> (require '[clj-java-decompiler.core :refer [decompile]])
nil
user=> (decompile (fn [] (1 (prn 2))))

// Decompiling class: user$fn_line_1__256
import clojure.lang.*;

public final class user$fn_line_1__256 extends AFunction
{
    public static final Object const__0;
    public static final Var __prn;
    public static final Object const__2;

    public static Object invokeStatic() {
        return ((IFn)const__0).invoke(__prn.invoke(const__2));
    }

    @Override
    public Object invoke() {
        return invokeStatic();
    }

    static {
        const__0 = 1L;
        __prn = RT.var("clojure.core", "prn");
        const__2 = 2L;
    }
}


// Decompiling class: cjd__init
import clojure.lang.*;

public class cjd__init
{
    public static void load() {
        new user$fn_line_1__256();
    }

    static {
        Compiler.pushNSandLoader(RT.classForName("cjd__init").getClassLoader());
        try {
            load();
            Var.popThreadBindings();
        }
        finally {
            Var.popThreadBindings();
        }
    }
}

nil
This is the most pertinent line: ((IFn)const__0).invoke(__prn.invoke(const__2));. Here we can see that in order for 1 (which is assigned to const__0) to be called as a function, it gets cast to IFn - before any of the arguments are evaluated.

Jim Newton 2026-05-26T09:36:49.852999Z

hmmm. do you think the documentation is misleading in this corner case?

Jim Newton 2026-05-26T09:39:14.739959Z

Side question. (maybe best for another thread???). You compiled (fn [] (1 (prn 2))), however I typed (1 (println 2)) at the repl. Does the introduction of (fn …) change what’s happening?

hrtmt brng 2026-05-26T09:41:10.900569Z

Your guess is wrong. For functions, all arguments are evaluated. But for macros not. So it must first find out if 1 is a function or a macro.

👍 2
Dave Liepmann 2026-05-26T09:46:39.657389Z

Docs don't seem misleading to me.

Jim Newton 2026-05-26T09:48:42.617409Z

@hbrng.computer yes the evidence seems to indicate that you are right. However, the documentation (to my reading) says otherwise. it says that it first checks whether it is a macro or a special form, otherwise it assumes it is a function call (without verifying). and then evaluates the operator and operands, then casts the operator to IFn and attempts to invoke the function with the already-evaluated arguments.

Jim Newton 2026-05-26T09:50:28.930449Z

@dave.liepmann i’m interesting in better understanding your point of view.

Dave Liepmann 2026-05-26T09:54:31.927269Z

>

Both the operator and the operands (if any) are evaluated, from left to right. 
> 
> The result of the evaluation of the operator is cast to IFn
Where's the lie? The sequence of operations, specifically when casting to IFn happens, is not part of the documented contract.

Jim Newton 2026-05-26T09:58:52.165999Z

@dave.liepmann so you don’t read the paragraph as a sequence of events in order, but just a randomly ordered list of things that happen? You are right that it does not explicitly say this is the order of events. Actually in English the sentence “the operands are evaluated” is ambiguous. it can mean “something evaluates the operands” or it can mean “the operands are in the state of already having gotten evaluated”. Danger of passive voice.

Dave Liepmann 2026-05-26T10:03:30.935599Z

I see how one could interpret it as ordered, but it's not. It's above my pay grade whether there's good reason to lock down the documented behavior but by default I'm skeptical.

Jim Newton 2026-05-26T10:05:00.892879Z

Is there interest in improving the documentation? I think the paragraph could be easily worded better. for example, it could say that “the order of evaluation of invalid forms is unspecified”

🤷 1
Jim Newton 2026-05-26T10:06:05.530579Z

this is expecially true given the decompliation shown above. the actual evaluation is left up to the java compiler which might even re-order some things in some situations.

p-himik 2026-05-26T10:10:38.630199Z

@hbrng.computer That check is done explicitly, not via casting. So it's not relevant. The code compiles correctly. It just cannot run.

p-himik 2026-05-26T10:12:53.515599Z

> Is there interest in improving the documentation? Yes, suggestions for documentation can be filed at https://github.com/clojure/clojure-site/issues. And all the info on contributions in general, including docs improvements, is here: https://clojure.org/community/contributing.

p-himik 2026-05-26T10:14:25.602439Z

> the actual evaluation is left up to the java compiler which might even re-order some things in some situations. Such a reordering should not change anything relevant to the thread.

Jim Newton 2026-05-26T10:28:25.977559Z

I’ve added an issue on clojure-site: https://github.com/clojure/clojure-site/issues/751 I’ve noted that some people do not disagree with my opinion. I didn’t note that in fact everyone disagrees with my opinion (smirk).

borkdude 2026-05-26T10:41:26.373629Z

you could also point students to clj-kondo:

$ clj-kondo --lint - <<< '(1 (prn :hello))'
:1:2: error: a number is not a function
linting took 7ms, errors: 1, warnings: 0

lprefontaine 2026-05-26T10:45:46.746909Z

Having wrote a Lisp evaluator as part of an assignment in the 80s (adding more special forms than the defaults aka fn and macros), I find this behavior normal. If it's not quoted the list is evaluated as an fn or macro expression and obviously the first arg needs to be evaluated before anything else can occur. You can't eval the args if you don't know which special form you're dealing with from the start...

borkdude 2026-05-26T10:46:45.810869Z

same behavior in SCI / bb btw:

user=> (1 (prn :hello))
java.lang.ClassCastException: java.lang.Long cannot be cast to clojure.lang.IFn [at <repl>:1:1]

p-himik 2026-05-26T10:46:49.409269Z

> the first arg needs to be evaluated before anything else can occur. The 1 is evaluated to 1. :) It's the cast that's the problem. And nothing requires the cast to happen before evaluating the rest of the arguments.

👍 1
lprefontaine 2026-05-26T10:49:42.342189Z

Being evaluated to a number and a list starting with a number is not a valid special form. Reporting this as an error is fine. At that point you still don't know what to do with the remaining arguments so why would you eval them ?

borkdude 2026-05-26T10:49:45.489199Z

CLJS:

cljs.user=> (1 (prn :hello))
:hello
Execution error (TypeError) at (<cljs repl>:1).
1.call is not a function

👍 1
😲 1
lprefontaine 2026-05-26T10:50:40.052179Z

Which to me is inconsistent....

lprefontaine 2026-05-26T10:51:14.039059Z

That cloud lead to bad side effects...

lprefontaine 2026-05-26T10:51:25.985989Z

... could...

borkdude 2026-05-26T10:52:44.719899Z

public static Object invokeStatic() {
        return ((IFn)const__0).invoke(__prn.invoke(const__2));
    }
I guess that could have been written as
var arg = __prn.invoke(const__2);
return ((IFn)const__0).invoke(arg)
Probably just not very well defined what should happen here. If you care about this specific case you can always use a let yourself

👍 1
lprefontaine 2026-05-26T10:54:35.996079Z

Agree that it should be clarified. I do not see any logic in the current implementation and a potentially harmful loop hole. That's my SCADA background haunting me 😂

p-himik 2026-05-26T10:55:52.986379Z

@lprefontaine The point is not the "why would you", the point of my message was to refute the "you can't eval". You can in some cases, including this one. It might make little sense (but someone could make it make sense in their specific deranged case since class cast exceptions can be caught and argument evaluations can have side effects), but it's feasible.

Jim Newton 2026-05-26T10:56:55.618109Z

@lprefontaine > You can’t eval the args if you don’t know which special form you’re dealing with from the start... Yes, but the documentations says that if it is not a macro and not a special form then it is assumed to be a function call.

borkdude 2026-05-26T10:57:59.553619Z

Yeah I guess the docs could clear up the order of the cast and evaluation.

lprefontaine 2026-05-26T10:58:02.240599Z

It defies logic to allow this to happen. The default to assume it's a function call is ok, what's not is to eval the args before knowing that it's a function and not a programming error.

borkdude 2026-05-26T10:58:37.384759Z

"clojure is designed for correct programs"

lprefontaine 2026-05-26T10:59:35.221579Z

Yeah but this is an area where most don't go deep...

lprefontaine 2026-05-26T11:01:01.624249Z

Think about a production issue, you have stack trace reporting that it's not a function but the args where evaluated with some persistent side effects 😵‍💫 The investigation will take for ever...

borkdude 2026-05-26T11:01:07.445969Z

@jimka.issy Maybe you shouldn't bother students with these edge cases? lolcry In my 16 years with Clojure this hasn't tripped me up once.

Jim Newton 2026-05-26T11:01:34.588229Z

I know that we live in a pragmatic world; however, I think documentation should be correct and not misleading at least when it is easy to be so. Ideally a programming language should be defined by its documentation, not by its specification. yea yea yea, I know that the world is not an ideal place.

👍 1
borkdude 2026-05-26T11:01:57.965289Z

oh I agree that the docs should be clarified.

Jim Newton 2026-05-26T11:02:03.719229Z

@borkdude, the situation arose when I asked myself: Do I really understand what the REPL does, and I realized. hmmm No I really dont.

👍 2
borkdude 2026-05-26T11:02:25.960229Z

congrats in teaching clojure btw. I did it too in 2011-2013

👍🏻 1
Jim Newton 2026-05-26T11:03:10.423479Z

@borkdude agreed, this is not something I should confuse the students with.

Jim Newton 2026-05-26T11:04:11.703379Z

@borkdude would you mind commenting on the github issue that the behavior is inconsistent clj vs cljs ?

lprefontaine 2026-05-26T11:04:48.777649Z

Never thought that this loop hole existed... taking note, ya never know when a truck will hit you sideways on your green light at a crossroads...😬

borkdude 2026-05-26T11:05:01.050349Z

I guess it's kind of host specific. since CLJS functions are real JS functions and casting isn't a thing in JS

Jim Newton 2026-05-26T11:20:32.397219Z

@lprefontaine > Never thought that this loop hole existed.. If you follow the Common Lisp community these kinds of corner cases discussions are commonplace. CL is defined by a specification and there are a hand full of implementations. Whenever two different implementations give different behavior people discuss what the specification says, and have discussions worthy of theologians about every jot at tittle of the specification.

👍 1
p-himik 2026-05-26T11:22:26.413649Z

C++ programmers have cursed the CL community in revenge for having to follow the Greenspun's tenth rule.

Jim Newton 2026-05-26T11:23:24.679669Z

I don’t follow C++, do they follow Greenspun’s 10th rule?

lprefontaine 2026-05-26T11:24:09.028159Z

My first Lisp was UCI Lisp. In the 80s. The specs were very clear as the implementation.

Jim Newton 2026-05-26T11:25:49.728109Z

I heard a funny discussion at Franz (a professional proprietary lisp compiler company). While implementing AllegroGraph, one of their developers commented that even though AllegroGraph is implemented in CL, it is also a victim of this rule.

😄 1
p-himik 2026-05-26T11:27:23.488969Z

@jimka.issy I meant that C++ is designed by a committee. I believe the rule predates C++, but I'm pretty sure C++ also follows it by its nature, although any negative epithet in the rule is likely to be magnified by the C++'s complexity.

Jim Newton 2026-05-26T11:36:28.674319Z

Back to the documentation excerpt …. I think that paragraph is also missing another very important case. reading that literally does not allow for (:x {:x 100 :y 200}). Or did I miss something?

Jim Newton 2026-05-26T11:37:11.707879Z

@lprefontaine how many implementations were/are there of UCI Lisp?

p-himik 2026-05-26T11:37:21.184199Z

> reading that literally does not allow for (:x {:x 100 :y 200}). Why not?

Jim Newton 2026-05-26T11:38:04.671769Z

it says that if the form is not a special form nor a macrocall, then it is interpreted as a function call where the first element is cast to Ifn.

Jim Newton 2026-05-26T11:38:27.720849Z

ahhhh, does :x cast to Ifn as a call to get ?

Jim Newton 2026-05-26T11:38:35.294769Z

that would be clever if that’s the case.

p-himik 2026-05-26T11:38:54.485899Z

Keywords are functions, yes.

p-himik 2026-05-26T11:39:02.458239Z

Associative collections are also functions (that includes vectors and sets). And symbols. Meaning, a symbol can find itself in a map.

borkdude 2026-05-26T11:40:46.204019Z

not sure if you also had this in scope in this thread:

user=> ((prn :foo) (prn :bar))
:foo
:bar
Execution error (NullPointerException) at user/eval3 (REPL:1).
Cannot invoke "clojure.lang.IFn.invoke(Object)" because the return value of "clojure.lang.IFn.invoke(Object)" is null

p-himik 2026-05-26T11:42:16.401779Z

The N-gon just became an (N+1)-gon!

Jim Newton 2026-05-26T11:43:35.867929Z

why is a NullPointerException unexpected? if you try to call nil as a function?

borkdude 2026-05-26T11:43:38.477859Z

The above happens because you can succesfully cast a nil to any type in JVM

borkdude 2026-05-26T11:46:09.983669Z

user=> ((do (prn :foo) 1) (prn :bar))
:foo
Execution error (ClassCastException) at user/eval9 (REPL:1).
class java.lang.Long cannot be cast to class clojure.lang.IFn

lprefontaine 2026-05-26T11:58:33.189729Z

@jimka.issy UCI Lisp was a derivative of Stanford Lisp 1.6 and in turn inspired 4 other implementations under various names. UCI Lisp ran on PDP architectures up to Dec-20s, I recall using it on both Tops-10 (DEC 10] and Tops-20 (DEC 20).

2026-05-26T13:38:55.548889Z

i would have anticipated the code to work like borkdude's suggestion (copied below), but i think it's more a feature/quirk of the jvm than of clojure

var arg = __prn.invoke(const__2);
return ((IFn)const__0).invoke(arg)

2026-05-26T13:42:42.221329Z

i suspect that the way you can nest calls means you'd have to rewrite/change the compiler a bunch to handle this "properly", for a somewhat small consistency fix

borkdude 2026-05-26T14:57:33.826069Z

The Clojure compiler has full control over this btw

hrtmt brng 2026-05-26T16:49:21.063869Z

Are you sure this topic matters? Calling a number is illegal. It is not a corner case. Language standards normally don't specify what happens in such situations. In C++ this is called undefined behaviour. It is intentionally undefined.

➕ 1
2026-05-26T17:37:43.657229Z

"does this topic matter?" any topic is worth discussion. if you don't like the topic, you don't have to engage.

SK 2026-05-26T10:48:09.107159Z

Which AI tool do you use together with Clojure? I've tried chatgpt and gemini via their web interfaces to ask for some simple functions working on tablecloth, XMLs, basic data structures but the results were pretty underwhelming.. eg regularly giving me code that includes non-existing functions and stuff like that. Which tool do you use and how?

olli 2026-05-26T10:51:34.976769Z

Maybe check out #ai-assisted-coding if you haven't already

SK 2026-05-26T10:52:31.664189Z

Thanks. For some reason it slack did not show that this channel exists at all.

p-himik 2026-05-26T11:03:24.505149Z

Slack doesn't join you to all its channels, unlike e.g. Discord. There are many channels here, but you have to search for them via the channel search tool.

👍 1
seancorfield 2026-05-26T12:45:20.861639Z

I'd recommend using tooling that integrates with your editor (e.g., VS Code and its GitHub Copilot) or a dedicated coding AI tool (e.g., Claude Code). The standalone AI chat apps / web have no context for your codebase/project, and that matters.

lprefontaine 2026-05-26T15:19:26.529129Z

I put several LLMs to trial, including local models but didn't get very far in terms of success in day to day use. It's almost a miracle to get error free code generated. You need an MCP to allow your agent to fix it, clojure-mcp. Kind of lame to have to rely on this, considering that Lisp code is a data structure but it's the only way. Of course this MCP is helpful for other things aside deo syntax validation. Maybe there's not enough training data out there. On the other hand since there's no boilerplate code, "typing speed" is not a thing to me. So I tackled more complex thing using AI, I am migrating an old ReactNative mobile app to ClojureDart/Flutter. The original app itself is pretty bad in terms of code reuse and I am in the process of fixing the generated code. Trying to get an LLM to improve the generated code reuse has been a mixed bag. For code review however it proved useful. My conclusion so far is that we need a better trained LLM if we want more than light code assistance. They're better at verbose programming languages like OOP. Next fall, I will put my hands on a Mac Studio on steroids and dive in this deeper using local models.

salam 2026-05-26T15:40:26.135629Z

i was shopping for a thin wrapper around java.net.http.HttpClient and eventually landed on https://github.com/babashka/http-client. seeing https://github.com/babashka/process “upstreamed” and included in Clojure 1.12, i'm thinking: wouldn't it be nice for future Clojure versions (that are based on Java 17+) to have a similar but built-in wrapper like babashka.http-client? 🙂

seancorfield 2026-05-26T15:48:49.737959Z

There's hato which is a thin wrapper around the Java 11+ HTTP client. https://github.com/gnarroway/hato That's what we use at work.

➕ 1
Alex Miller (Clojure team) 2026-05-26T15:50:14.786459Z

it's built into the jdk since 11 https://docs.oracle.com/en/java/javase/11/docs/api/java.net.http/java/net/http/HttpClient.html

salam 2026-05-26T15:50:55.558259Z

yes, hato was amongst the options that i considered but development on it seems to have stalled…

seancorfield 2026-05-26T15:51:24.792699Z

A lot of libraries in Clojure are simply "complete" and don't need additional development 🙂

Alex Miller (Clojure team) 2026-05-26T15:51:40.869119Z

I don't use any of these often enough to know if hato provides enough additional help that including something like it would be worth doing in core (once we move past JDK 8 as a base), but I think that would be good thing to consider

❤️ 1
Alex Miller (Clojure team) 2026-05-26T15:51:53.831149Z

would welcome an Ask Clojure question along those lines

seancorfield 2026-05-26T15:53:06.169019Z

I would also assume that clojure.java.process was independently designed by the core team, rather than being in any way an "upstreaming" of babashka.process...

salam 2026-05-26T15:54:10.112579Z

oh, Opus somehow thought it was modeled after babashka.process…

Alex Miller (Clojure team) 2026-05-26T15:54:15.626659Z

yes, I would expect a similar re-examination for something like the http client, but who knows

Alex Miller (Clojure team) 2026-05-26T15:54:50.473719Z

there are similarities to babashka process for sure but it was a clean impl

seancorfield 2026-05-26T15:55:00.054479Z

> Opus somehow thought... "Opus somehow hallucinated..." FTFY 🙂

💯 1
seancorfield 2026-05-26T15:55:26.522119Z

(don't get me wrong: I use Claude LLMs all the time but...)

seancorfield 2026-05-26T16:01:19.178099Z

Definitely worth a request on https://ask.clojure.org -- once Clojure 1.13 drops JDK 8 support, it would be nice to have an HTTP client in the core library.

salam 2026-05-26T16:01:46.787089Z

oh, i see… good to know. i should have gotten my facts straight before saying that… didn't mean to discredit the core team. anyway, it sounds like there is a possibility for this. i'll go ahead and create a request on Ask Clojure.

p-himik 2026-05-26T16:09:21.622749Z

> A lot of libraries in Clojure are simply "complete" and don't need additional development 🙂 I wouldn't say Hato is complete though...

seancorfield 2026-05-26T16:12:38.841599Z

I wonder if any HTTP client library could ever be complete? Perhaps "sufficiently complete" (and you can drop down to Java interop as needed) is the best we can hope for with Java API wrappers? 🤔

p-himik 2026-05-26T16:15:59.901699Z

Well, that reasoning can be applied to absolutely any library. :D In the case of Hato, I myself consider it using cheshire to be a deal breaker when it comes to deciding whether it's complete or not. I'd rather see serialization pluggable than hard-coded.

borkdude 2026-05-26T16:17:02.228229Z

I remember Alex and I had some back and forths about bb.process when he designed c.j.process, about the option map argument etc.

seancorfield 2026-05-26T16:17:07.316269Z

Cheshire is optional with Hato. We use clojure.data.json -- we don't have Cheshire anywhere in our deps tree at work, as I recall. I worked hard to remove it 🙂

💯 1
👌 1
borkdude 2026-05-26T16:17:31.833999Z

bb.http client is very similar to hato but has some extra things I needed and some things I didn't like it doesn't have :)

2
seancorfield 2026-05-26T16:18:15.628589Z

Hato has zero dependencies, which is part of why we switched to it (from clj-http).

p-himik 2026-05-26T16:21:05.707809Z

Well, yeah, but then the value-add of the library drops significantly if you can't use :as. It would be around 20% more lines to just use the JDK stuff directly, last time I checked it in my own code. It's already slim as it is, given how functions like build-http-client take 40 lines but do basically nothing. :) Maybe I'm just even less of a fan of Java wrappers than you are. :D

seancorfield 2026-05-26T16:21:44.621019Z

Probably. And I'm already not really much of a fan of them 🙂

2026-05-26T16:23:01.121089Z

i think http is super complex compared to the other mentioned examples (which is why there are so many libraries). I think even if you want to just do a light java.net.http wrapper + clojure maps for request/response, that road eventually leads to needing something like an interceptor pattern or accepting some ways http is legitimately used will be less performant or convenient than necessary (not that i wouldn't love to see the core team's take on it)

seancorfield 2026-05-26T16:23:02.835179Z

But Hato has a (mostly) clj-http-compatible API so it was an easy switch, and we use explicit JSON conversion, in and out, via c.d.j so...

seancorfield 2026-05-26T16:25:42.621839Z

Somewhat ironically, we had one old app that had to stay on JDK 8 for years, and so we wrote our own wrapper for Hato that we could swap out for a JDK 8 compatible client with the same calling API, so we're another layer removed from Java interop because of that.

😄 1
seancorfield 2026-05-26T16:26:25.615939Z

@jjttjj I agree that it's a much harder problem than it looks on the surface...

🙌 1
borkdude 2026-05-26T16:27:30.533669Z

bb.http-client also has zero deps (based on java.net.http) and everything is pluggable. it does have some good defaults which are overridable. I started from scratch but with two examples in mind. one was schmee's https://github.com/schmee/java-http-clj and hato (and clj-http for how it named stuff), but I wanted it to be minimal unless I needed something. And I didn't want to depend on either project and be at the mercy of "is this project maintained" or wait 2 months for something to be merged

❤️ 1
🤔 1
borkdude 2026-05-26T16:29:02.558759Z

also one strong requirement was that it worked well with graalvm native-image. only schmee's project did so out of the box at the time

Joe Lane 2026-05-26T17:17:09.746029Z

The Cognitect http-client is changing to the jdk built-in http-client in the next release, which we are about to ship. And it should be open-source, in that the source should be in the JAR in maven-central shortly.

👍🏻 1
👀 2
salam 2026-06-08T01:54:50.537449Z

OK, at long last, here is the request: https://ask.clojure.org/index.php/15129/consider-including-wrapper-around-httpclient-versions-clojure please vote (and participate in the discussion) if you like this proposal, too. 🙂

seancorfield 2026-06-08T13:34:20.625029Z

Thank you! Upvoted.

Felipe 2026-05-26T18:01:33.915659Z

I enjoyed the library curation discussion on the Dev Call. clojure.land was news to me, but I knew about prior art such as the https://www.juxt.pro/radar/ and the https://www.clojure-toolbox.com/. Should these newer tools be be included https://clojure.org/community/libraries I'll add another data point: I was recently exploring Swift and was pleasantly surprised by their unofficial https://swiftpackageindex.com/groue/GRDB.swift: it gathers the license, versions, dependencies (transitive or not), snippets for package managers, API docs etc. A good source of inspiration IMO, although I haven't used it in anger

2026-05-27T17:47:00.437599Z

I updated Clojure Land to pull in the GitHub topics and use them for search. So, for example, now searching for "encrypt" will surface Tempel

🎉 4
neumann 2026-05-26T18:13:15.566659Z

Thank you for the links!

Alex Miller (Clojure team) 2026-05-26T18:13:27.823819Z

I've added clojure.land to the page. the juxt radar is pretty old

👍 1
phronmophobic 2026-05-26T18:50:26.622849Z

It's not very pretty, but I think https://phronmophobic.github.io/dewey/search.html gives good results when searching for clojure libs. https://cloogle.phronemophobic.com/doc-search.html is useful for finding specific functions (although it's about a year out of date at the moment). (example query, https://cloogle.phronemophobic.com/doc-search.html?q=create+and+return+a+temporary+file).

1
seancorfield 2026-05-26T19:11:19.559579Z

Seems like some of what you're after is sort of clojars + cljdoc and those two both have search functionality and they cross-link for specific versions?

seancorfield 2026-05-26T19:12:21.789499Z

I guess what's missing there would be some sort of "curation" / "popularity" index?

phronmophobic 2026-05-26T19:13:00.888009Z

Also, clojure libraries can be consumed as git deps, so it would be missing all the usable libraries that aren't on clojars.

seancorfield 2026-05-26T19:14:21.491169Z

Yeah, true. At some point, we may get (popular) git deps on cljdoc but that requires quite a bit of work...

seancorfield 2026-05-26T19:16:14.925259Z

A problem with nearly all the "curated" lists is that they are somewhat at the whim of whoever cares enough to submit a PR... and they can easily get outdated when new, more popular libraries appear but don't get added (and, in particular, do not replace the older, less popular library).

phronmophobic 2026-05-26T19:18:14.944219Z

You could use the various various sources of activity (eg. zulip, github, clojars, etc) to approximate recent usage.

seancorfield 2026-05-26T19:21:38.173549Z

http://clojure-doc.org used to have a "curated" list of libraries but it got very out of date. In 2023, when I was overhauling that site, one of the big pieces of work in Q2 was going through everything listed on clojure-doc and the toolbox site, and making sure everything that was at least reasonably maintained got added to / updated on the toolbox site, so I could retire the clojure-doc version.

Alex Miller (Clojure team) 2026-05-26T19:22:09.536149Z

I believe this is ezactly what clojure.land is trying to do with multiple activity metrics

seancorfield 2026-05-26T19:26:28.194779Z

Yeah, it may well be the "best" index we have for now...

seancorfield 2026-05-26T19:28:20.002189Z

Looks like Brett is pretty active about adding project metadata from the weekly Deref! That makes me feel a lot better about it as a sustainable option!

neumann 2026-05-26T20:55:45.079859Z

@batoms and I have been talking, and yes, he's been using the Deref as a source.

1
neumann 2026-05-26T20:56:41.430259Z

I like clojure.land and the work he's put into it.

neumann 2026-05-26T20:58:21.745859Z

What I had suggested to Brett was the idea of develop out different "signals" to try an help prioritize results. (Eg. GitHub stars, recent commits, frequency of commits, mentions in other projects, closed issues, etc).

neumann 2026-05-26T20:58:51.330879Z

A similar idea to what @smith.adriane is suggesting.

phronmophobic 2026-05-26T21:00:12.708959Z

One thing I noticed when comparing clojure land with https://phronmophobic.github.io/dewey/search.html is that clojure land is missing the topics from github which seem to be more comprehensive. I think it's also useful to be able to search by repo author.

neumann 2026-05-26T21:01:30.415599Z

Yeah, searching by author is nice for sure. Eg. I love @christian767's work, so anything he puts out is immediately interesting to me.

❤️ 1
seancorfield 2026-05-26T21:01:44.786629Z

When I migrated stuff from clojure-doc to the toolbox, the first PR I submitted was to get the description of the library surfaced as a tooltip, so I agree with Adrian on adding the "about" from GH if poss.

seancorfield 2026-05-26T21:05:16.827199Z

Re: signals -- sounds good! Stars is a metric that rarely if ever goes down, so something that got a lot of stars back in the day but isn't as popular today (e.g., Om) should be lower down the list. Same with downloads for things that are rarely used on their own but come in transitively as part of something that is wildly popular (Riddley, Cheshire, and Potemkin may be examples?). Finding the right weighted metric is hard of course, and subjective 🙂

phronmophobic 2026-05-26T21:06:53.081239Z

maybe it should have reviews like yelp, google maps, amazon, etc 😛

neumann 2026-05-26T21:09:44.899269Z

Yeah, I think experience reports (aka "reviews") are another data point that could be considered. What's tough about that is moderation.

neumann 2026-05-26T21:10:27.905689Z

If someone posts some kind of flaming takedown full of personal attacks, there has to be a process to address that, for example.

neumann 2026-05-26T21:11:27.440439Z

I think there are lots of different angles on a decision, so if we can help surface the data for each of those angles, people can make up their mind.

2026-05-26T21:19:43.272609Z

> Yeah, true. At some point, we may get (popular) git deps on cljdoc but that requires quite a bit of work... I played around a lot of with the popularity index on Clojure Land and to try to get something that felt honest. Ultimately I landed on a heuristic based on stars, downloads per day from clojars and with a staleness decay based on the last release date so that projects that were really popular 10+ years ago like Om don't appear more popular than more recent projects.

2026-05-26T21:24:13.785109Z

One thing I noticed when comparing clojure land with dewey search is that clojure land is missing the topics from github which seem to be more comprehensive. I think it's also useful to be able to search by repo author.In Clojure Land I'm using the project description from the GitHub by default but it also allow it to be over written in the projects.edn file. For the Github topics they just felt too arbitrary and uncurated so I started with the categories from the Clojure Toolbox for the Clojure Land tags and then worked from there. To be honest these days I just have the LLM assign tags based on the project README and rarely try to curate them myself.

seancorfield 2026-05-26T21:25:15.033479Z

Ah, I misunderstood what was meant by "topics" there... so, what exactly is that?

2026-05-26T21:31:33.326289Z

They're the topics you can assign to a Github repo like the "jdbc" topic assigned to the seancorfield/next-jdbc repo In Clojure Land I call them "tags". In Dewey you can view projects by topic but since they're managed by the repo owner then it can get really messy, e.g. on the first page of the https://phronmophobic.github.io/dewey/topics.html there is 12-factor, 12factor and 12factorapp

seancorfield 2026-05-26T21:34:12.159549Z

Haha... totally forgot I'd added that... and so I had forgotten those "tags" were called topics. Thank you. Yeah, I don't think I've added topics to any of my other repos.

phronmophobic 2026-05-26T22:09:07.767909Z

I almost never use dewey's topic page. For my purposes, I'd rather find a library and decide not to use it then not find a relevant library. As an example, I was looking for an encryption library and searched "encrypt". With dewey search, I found tempel as the second result and it doesn't appear as a result on clojure.land, despite tempel being in clojure.land's list. I can give a few more examples where including extra topics would help provide more results on clojure.land if that would be helpful.

phronmophobic 2026-05-26T22:10:26.200459Z

The dewey search stuff doesn't try to curate and curation can mean different things to different folks.

2026-05-26T22:19:34.754599Z

I wasn't trying to call out Dewey here so sorry if it came across that way. My intention was that using GitHub topics without some kind of curation can get really noisy in the project cards in Clojure Land. I've intentionally tried not to curate Clojure Land projects other than the tags and trying to limit it to open-source frameworks and libraries rather than applications built with Clojure. I could definitely pull in the topics from GitHub without displaying them and put them in the search index. I was hoping that fuzzy searching on the combination of project name, description and tags would be enough but I guess there's always going to be misses.

👍 1
2026-05-26T22:21:26.816179Z

And PRs to improve the project list or tags are always welcome 😉

phronmophobic 2026-05-26T22:22:10.028179Z

Yea, I didn't take any offense. clojure.land looks great. I hope I wasn't coming off as negative.

phronmophobic 2026-05-26T22:24:46.537819Z

There are some tradeoffs between comprehensiveness vs curation and I wasn't sure how clojure.land was navigating that. You can get all the tags that dewey knows about in the weekly https://github.com/phronmophobic/dewey/releases/ under deps-libs.edn.gz. I'd be happy to make a PR that updates all the tags, but I assume you would want more curation that just what is on github.

phronmophobic 2026-05-26T22:25:44.189359Z

The whole point of the dewey project is to try to make it easier for tools like clojure.land so if there's some data that would be helpful, I can look into adding it.

2026-05-26T22:29:31.016509Z

I already have a process to pull data from GitHub daily so I could incorporate fetching the tags into that.

phronmophobic 2026-05-26T22:32:19.030129Z

I noticed some deps don't have deps coordinates. Is that because they're not in clojars? If you pull git tags, you can often generate git dep coordinates for libs not in clojars.

phronmophobic 2026-05-26T22:49:54.176179Z

Finally got to the part of the video about curation. It seems like it would be interesting idea to check the obvious places to see if a repo has commits authored using AI.

Felipe 2026-05-26T22:55:23.358139Z

most AI-aided repos seem to have Claude as a contributor. knowing about those is a useful signal to me

phronmophobic 2026-05-26T22:56:17.199109Z

I believe tools like Codex also attach co-author metadata to commits.

phronmophobic 2026-05-26T22:57:13.677929Z

I think having little badges for the obvious stuff makes sense. I'll probably try to add that in the next update to dewey.

neumann 2026-05-26T23:38:41.915299Z

Seems likely if there is an AGENTS.md or CLAUDE.md file too.

➕ 1
phronmophobic 2026-05-26T23:44:33.870409Z

Inverse document frequency of em dashes and delves

😂 1
seancorfield 2026-05-27T02:50:04.409919Z

I just looked over some of my GitHub projects where I've definitely used AI in the last six months (including a PR entirely authored by Copilot/Claude) and there's no indication in the commits. A couple of commits have a note that they were Reviewed by Copilot (true: I often run my own code through a Copilot review to see if it suggests improvements to naming or docstrings). At work, I see stuff like:

WS-15438 wire up tx status call on redirect Co-authored-by: Copilot copilot@github.com Signed-off-by: Sean Corfield sean@worldsingles.com
in a lot of commits lately, so I think the "co-author" credit is something that is a fairly recent change in Copilot. So there's another signal to look for.

seancorfield 2026-05-27T02:53:01.603239Z

Also, there are no AGENTS.md or CLAUDE.md or whatever files in any of my repos -- everything is under .github:

seancorfield 2026-05-27T02:53:43.346349Z

(I have that setup in several of my OSS projects locally -- I just haven't committed those files 🙂 )

neumann 2026-05-27T03:51:45.091969Z

Ideally, I would hope people would self-report AI usage, but some usage would be really difficult to detect for sure.

phronmophobic 2026-05-27T03:55:25.960439Z

I think the people who care most about having a standard way to report AI usage are people who want to avoid AI altogether, which makes it harder to standardize. Further, companies like github are incentivized to avoid making it easy to discourage AI usage. I think it's ok if the signals aren't full-proof.

➕ 1
emccue 2026-05-26T19:27:56.227379Z

What would folks say is the current leading OAuth library to use for clojure? I need things from acronyms I don't understand like "DPOP" and "PAR" and to integrate with an app via these shenanigans https://clients.dev . I've been using ring-oauth2 and tried to vendor it to get it working for this but I'm at the limits of how much i can do without leaning forward in my chair

emccue 2026-05-27T15:55:50.603499Z

(defn- send-dpop-par-request
  [par-endpoint proof-factory auth-req nonce]
  (let [par-req         (PushedAuthorizationRequest. (URI. par-endpoint) auth-req)
        http-req        (-> par-req (.toHTTPRequest))
        _               (HTTPRequest/.setDPoP http-req
                                              (DPoPProofFactory/.createDPoPJWT
                                                proof-factory
                                                (-> (HTTPRequest/.getMethod http-req)
                                                    (Enum/.name))
                                                (HTTPRequest/.getURI http-req)
                                                nil
                                                nonce))
        http-res        (-> http-req (.send))
        par-res         (PushedAuthorizationResponse/parse http-res)
        {:keys [par-res
                nonce]} (or (and (PushedAuthorizationResponse/.indicatesSuccess par-res)
                                 {:par-res par-res
                                  :nonce   nonce})
                            (let [error-object (-> par-res
                                                   (PushedAuthorizationResponse/.toErrorResponse)
                                                   (PushedAuthorizationErrorResponse/.getErrorObject))]
                              (if (and (nil? nonce)
                                       (= error-object OAuth2Error/USE_DPOP_NONCE))
                                (send-dpop-par-request par-endpoint proof-factory auth-req
                                                       (or (some-> http-res
                                                                   (HTTPResponse/.getDPoPNonce))
                                                           (Nonce.)))
                                (throw (Exception. (str "Pushed authorization request failed: "
                                                        error-object))))))]
    {:par-res par-res
     :nonce   nonce}))
I've lost the mandate of heaven

lukasz 2026-05-27T15:57:23.886039Z

lgtm

dpsutton 2026-05-26T19:33:51.702689Z

former coworker of mine wrote this if it might be helpful: https://github.com/edpaget/oidc-provider

dpsutton 2026-05-26T19:34:00.497799Z

it’s been quite helpful for us

emccue 2026-05-26T19:47:42.213539Z

is this for making an oauth server? i'm more looking for a client https://github.com/weavejester/ring-oauth2

lukasz 2026-05-26T19:51:33.937249Z

I recently dropped ring-oauth2 - we run into too many papercuts, so if you only have one provider to support implementing basic code exchange flow is not that complicated

emccue 2026-05-26T19:52:29.742979Z

i am still on the atproto thing I was in the thread a few posts above https://clojurians.slack.com/archives/C03S1KBA2/p1779200079212939

emccue 2026-05-26T19:53:18.502949Z

and there is something which i can base off of, but idk how i feel about every choice here https://github.com/atproto-clj/atproto-clj/blob/main/src/atproto/oauth/client.cljc

emccue 2026-05-26T19:53:28.523649Z

but unfortunately i do need everything else

emccue 2026-05-26T19:53:49.149439Z

plus the extra annoyance of not knowing the url to send people to until "its too late"

emccue 2026-05-26T19:54:06.239269Z

what did you drop it in favor of? just homerolling it?

lukasz 2026-05-26T19:56:30.061739Z

Yeah, it wasn't that hard because I had most of the necessary bits already working - it's not the first time when I run into this, so the process boiled down to reimplementing the callback and code exchange flows - that file you linked to is more or less what you'll need

emccue 2026-05-26T19:57:13.155489Z

i'm gonna see if theres a way i can make spring security do this and then jam it in a ring flow

lukasz 2026-05-26T19:58:46.508979Z

I have a sketch of a library to facilitate the oauth2 flow and the user provides ways of storing and recalling the session, handling routing - but it's far from being usable

emccue 2026-05-26T20:29:01.265929Z

spring security cannot do this

emccue 2026-05-26T22:13:35.806309Z

com.nimbusds.oauth2 is promising, i am ripping out the impl of ring-oauth2 and replacing it with that. If i can fully lobotomize it and keep tests working I will integrate dpop and par then i'm good

emccue 2026-05-29T11:33:39.812359Z

I'm so tired chiefs, but i got it

emccue 2026-05-29T11:33:58.495459Z

https://jvm.mccue.dev if you want to see a working login page

👀 1