beginners

seancorfield 2026-05-09T22:36:28.548059Z

Do you struggle with some of Clojure's error messages? I'm writing an nREPL middleware that rephrases a lot of exceptions to try to make them more beginner-friendly and I'm interested in hearing from new-to-Clojure folks about exceptions that they've run into and gone "WUT?" Continuing in a 🧵

❤️ 22
2026-05-11T12:18:09.711459Z

Happy to see! I remember giving this a try some time ago, also first evaluating doing it at nrepl middleware level but the deciding a "dev compiler" (a beginner friendlier fork of Clojure) had more pros and almost no cons. Listing them here from the top of my mind : Pros : • much more powerful and more reliable since you can modify anything in the compiler • not dependent on nrepl • simpler setup (I think swapping the compiler is easier than the middleware setup) • things that work really well can be adopted eventually by the official compiler Cons : • looks scarier to swap the compiler, but since it is just a dev thing, you are still running your tests and prod with the official compiler • those errors will not show in a prod repl

hrtmt brng 2026-05-11T18:38:38.253549Z

Are you sure, this is feasible? For example this message:

Error printing return value (NullPointerException) at clojure.core/name (core.clj:1610).
Cannot invoke "clojure.lang.Named.getName()" because "x" is null
or
Execution error (ClassCastException) at svnclient/eval20199 (sncclient.clj:92).
class java.lang.Long cannot be cast to class clojure.lang.Named (java.lang.Long is in module java.base of loader 'bootstrap'; clojure.lang.Named is in unnamed module of loader 'app')
Isnt there a principal problem with error message in Clojure? We all agree, that the error messages are bad. Maybe there is a reason. Perhaps, because internally error messages come from the JVM? We work in Clujure but get error messages from the JVM, which is a complete different world. And the JVM does not tell us what all these classes, inheritance, instantiation messages in the Clojure world mean. Trying to retroactivelly synthesise this from the JVM is a hard job and may even be JVM or Clojure version dependent. Look at Jank. It does it principally different, and surprise, it produces better error messages (https://jank-lang.org/blog/2025-03-28-error-reporting/). Or look at linker errors in C. These are really hard to read. Why? Because the linker is language independent and linker messages are lower level. If you want to improve this, you must run after the linker and not after the compiler. But running after something is a frustrating job. So this explains what for me is missing, when trying to understand Clojure messages. They are not about Clojure.

seancorfield 2026-05-11T18:49:09.524559Z

@hbrng.computer I'm guessing you did something like this:

user=> (name nil)
Tried to call .getName() on a keyword or a symbol, but was given 'nil', possibly in x (which may be in code you are calling), at user/eval18707 (REPL:1) - runtime error ('nil' encountered).
user=> (name 42)
Expected a keyword or a symbol, but was given a number, at user/eval18709 (REPL:1) - runtime error (unexpected type).
That's what the rephrase middleware currently produces. I haven't tackled the namespace/function location reporting yet.

seancorfield 2026-05-11T18:52:47.741659Z

To @jpmonettas point, yes, if you fork Clojure itself, you can do a lot more to improve error messages, by adding extra checks all over the place -- at the cost of performance. This nREPL middleware replaces the default repl-caught function and uses modified versions of clojure.main/err->msg and clojure.main/ex-str (but relies on the existing Throwable->map and ex-triage work done in Clojure 1.10). It doesn't affect *e so you can still get the full, original stacktrace if you need it (including the original error message). This is intended to make the dev experience better -- and you can provide your own mappings and rephrasing in an EDN file on the classpath if you want.

seancorfield 2026-05-11T18:55:38.501669Z

And yes, to @hbrng.computer point, some of the more obtuse Clojure errors are from Java stuff deep in the implementation, where Clojure's approach -- for performance reasons -- is just to "try something" and let it throw on failure (`ClassCastException` being one of the worst offenders).

2026-05-11T18:59:51.113769Z

@seancorfield In my dev compiler experience (with ClojureStorm) putting code that could affect perf under a flag that can be easily disable from the repl provides the best of both worlds. Assuming these tools are targeting beginners most of the time perf doesn't matter at dev time, and when it does (like when profiling) a simple command at the repl could make the compiler behave mostly like the unmodified one.

seancorfield 2026-05-11T19:01:02.934679Z

As for pros and cons of using a Clojure fork, a) I wouldn't want to maintain a fork (`rephrase` is under 200 lines of code) and b) if I did a fork, folks couldn't use both my fork and ClojureStorm, whereas they could use rephrase, as nREPL middleware, with ClojureStorm if they wanted.

seancorfield 2026-05-11T19:01:48.731689Z

(and the largest piece of rephrase is a modified copy of clojure.main/ex-str 🙂 )

2026-05-11T19:06:34.720019Z

For b) you have a point, dev compilers don't compose, but they could be merged into one 🙂 . For a) I think probably both approaches need to follow compiler changes, since I don't think the current error reporting is part of any contract that can't randomly change. The Clojure compiler codebase has been so stable that after like 4 years of ClojureStorm maintaining the fork has been mostly just rebase + re run the tests.

2026-05-11T19:12:47.313119Z

With the "could be merged into one" I mean envisioning a dev compiler project that can do all sort of experimental stuff for helping at dev time, from tracing, to better error messages, etc. Using all the freedom you get from knowing it is only a dev tool. It can be a practical tool while also a place to try things that could eventually be adopted by the official compiler.

2026-05-19T05:41:07.597979Z

Sweet, cant wait to try it out. That might make onboarding much easier for new people

seancorfield 2026-05-19T12:16:41.316749Z

@aaronrebmann Cool. There's an 0.1.0-SNAPSHOT release available right now. Hoping to cut a 1.0.0 this week.

🔥 2
2026-05-19T13:43:13.587729Z

Btw. Is it babashka compatible by any Chance?^^

seancorfield 2026-05-19T14:56:32.297339Z

Not yet. bb is missing some pieces that it needs. An issue is open for that.

seancorfield 2026-05-19T14:57:07.582269Z

I have a bb branch in the repo but it is failing for now. I've talked about it in #babashka

seancorfield 2026-05-09T22:38:41.754539Z

You can either respond here or in the #rephrase channel. Setup might be non-trivial but I'm working on documenting that better and looking into how we can automate the use of this middleware... The library is https://github.com/seancorfield/rephrase if you want to look at the docs/source or try it out directly.

seancorfield 2026-05-09T22:39:30.180549Z

In the meantime, feel free to drop your confusing exceptions into this thread (or in #rephrase) and we can talk about whether/what this middleware would do for you.