beginners

Thomas McInnis 2026-04-05T00:43:14.585389Z

I’m converting a ~12k loc Java POC service into Clojure handlers that a thin Spring service will call. The experience has been wild. At just 3.5K loc and basically done, I keep thinking “is this it? is this how I’m meant to build this?“. I’m probably going to get the answer “yes that is exactly what its meant to look like” but I’m feeling kind of naked here. People in my org are likely to ask ’is this enterprise ready” and I don’t have the experience to answer. Every handler threads a map through sub-functions with a ‘step’ HOF that short-circuits and lets the map fall through on the presence of :error then throws to get caught by the Spring app and use the globally defined responses. (I realise its a variant of the railway approach https://clojurians.slack.com/archives/C053AK3F9/p1775257995721879?thread_ts=1775242927.737189&cid=C053AK3F9). The first step and last step(s) are side-effecty, everything else is pure. So: is this the kind of thing people tend to write themselves? Is there a library in widespread use for this sort of thing?

Thomas McInnis 2026-04-06T10:41:41.606909Z

thanks heaps for that walk-through @didibus - I think what you have described is where I’ll probably end up as complexity ramps. I can see immediately that this would be preferable when an handler may have branching logic and a linear flow over a map may crumble! Right now everything is top/tailed by side effects, but in the future I expect that will not be the case Also just started playing wiht Malli and can see where I might benefit from adding that on the boundaries

2026-04-06T03:01:34.775819Z

It is a common pattern yes. And using Spring as a thin framework is also fine and helps a lot with compatibility in a mostly Java shop. I'd be cautious about two things: 1. The use of HOF, very easy to get into abuse and making things harder to debug and understand. I don't fully follow how it's leveraged, but I'd be careful here, overuse of HOF is a common beginner pitfall. The risk of HOF is that it makes debugging harder, they don't show up in th call stack and all as cleanly. And their "meta" nature, it's hard to imagine in your head, when you read the code you never see the resulting function, you follow this trail of one function wrapping or using another and so on, and it means you have to mentally keep track of all this and remember what each does for the final dynamic composition. If the pattern is not extremely obvious, clear, and consistent, they grow into massive puzzler. Documenting the HOF usage pattern goes a long way as well for newcomers (and AI agents too) 2. Using a single map that passes through the entire stack call chain from top to bottom can start to suffer from coupling everything too much and making it more opaque and unclear what is available or required in every place. Think about it. If A calls B that calls C. If C is going to use key :a and :b and B calls C, but key :b comes from A... When you're looking at C, first of all, how do you even know what C requires or uses optionally from the map? And even if you did know, when looking at B, how do you know if the map it passes to C has everything C needs? And how do you know what's in the map at the time B has it? Those are the challenges that eventually pop up with this pattern.

2026-04-06T03:23:14.351269Z

Some people still make that pattern work. But what I personally prefer is a little different. Following my prior example. You'd have a top-level orchestrating function, likely the entry function to the handler, the first thing Spring calls. Now instead of having A call B then C with a big map passing through them. You have the orchestrating function call A where A declares exactly the parameters it needs (both required and optional) and the parameters it returns. Then A doesn't call B, but returns to the orchestrating function. Now the orchestrating function calls B where just like for A, B declares exactly it's inputs/outputs, and it doesn't call C but returns to the orchestrating function. And on it goes for as many steps you have. They can all take a map and return a map if you want, but you destructure and don't use :as so that it's explicit (or use malli/spec or something of that sort to clearly describe the inputs/outputs). The orchestrating function can just merge each result back into a map or threads. But at least everything is more explicit and it's easier to look at this orchestrating function and see the order and look at the signature or spec of each thing it orchestrates to know like what will for sure be available at some point in the orchestrating chain or optionally could be there or not. Or the orchestrating function can just explicitly map from one to the other by getting only the keys needed and passing them to the next, and so on, that's even more explicit. This also allows you to solve the more complex cases where having side effects only at the start and at the end doesn't work, and you need to perform a side effect somewhere in the middle as well, because the orchestrating function can orchestrate that side effect and all that comes with it, error handling, retries, and so on. So those A, B and C that this orchestrating function calls, they can either be pure or exclusively side effecting, and they can be orchestrated in any way. With all things, there are tradeoffs, so I'm not saying this pattern is always better, but in general, and for more complex handlers or service, personally I've found this pattern takes you further before things get hard to reason about or change.

❤️ 1
john 2026-04-05T00:48:00.620469Z

Was it gen'd? If so, you're tasting some of the concision you can get with Clojure. Those numbers sound about right, in terms of code size reduction. But if it's gen'd then there's likely to be many dozens of mistakes and bad smells and things that need to get cleaned up. Probably would need lots of work.

john 2026-04-05T00:48:22.824989Z

Like, to get merged to main in a clojure dev shop

john 2026-04-05T00:52:18.998499Z

But yeah, LLMs are surprisingly good at Clojure, one shotting things. It'll generally keep things at least immutable, without too much cheating, which is nice.

Thomas McInnis 2026-04-05T00:56:37.078239Z

Oh both substantially gen’d: the Java with oversight from a Java dev, the Clojure with a very tight rein by me, however I am a beginner and have absolutely no doubt it’s full of smells 🙂 The smells I am ok with tackling incrementally as I learn, but I guess I just want reassurance that the pattern I have implemented is reasonable. It’s like if I was implementing an API in node, I’d slap in ExpressJS on it and would have a fairly obvious way of doing everything and everybody would look and nod along and say “ah yes, Express, fine” or whatever

john 2026-04-05T01:02:37.176489Z

That's really cool! I love doing that. Using LLMs to explore and learn about tech. Have you heard about Ring?

john 2026-04-05T01:03:55.835809Z

I'll warn you though...

john 2026-04-05T01:04:07.774199Z

Some people are not as exciting about the LLMs

john 2026-04-05T01:04:26.517769Z

And you might get some push back

john 2026-04-05T01:04:42.932769Z

Some folks see it as lazy

john 2026-04-05T01:08:12.709939Z

So I'd caution beginners in any tech scene to be careful with how many "LLM instigated questions" where folks might feel like they're having to push back against badly gen'd things, or having to engage in 3rd party prompting, or having to help people unlearn bad things they learned from an LLM. So I'd try to target your questions specific to the semantic and only bring in the LLM context if it's specific to the problem or is relevant. I think it is here, but just know that some folks are super tired of hearing about it so they might give negative feedback.

👍 1
john 2026-04-05T01:09:28.036909Z

Anyway, that's just a sidebar PSA... I think Ring is what you want to look into and there's a lot of folks here that know about other options too.

seancorfield 2026-04-05T01:16:41.122889Z

Yes, 12K to 3K sounds about right for a Java -> Clojure conversion. Clojure is extremely concise compared to Java. Even heavy interop is much more pleasant in Clojure than doing the same thing in Java. Your pipeline with :error sound very much like our photo processing pipeline at work: lots of stages, threaded, a map with :error when things go wrong but otherwise accumulated information as each stage succeeds.

❤️ 1
Thomas McInnis 2026-04-05T01:32:00.772399Z

Well that’s encouraging thanks @seancorfield - using the pattern I think I finally ‘got’ the true power of immutability and persistence of the data structures. @john I appreciate the PSA. In my org I am simultaneously one of the heaviest LLM users, and one of the loudest LLM cynics. You cannot outsource thinking, and LLMs give such a good simulacra of thinking that people are lulled into believing what it tells them. (Terrifying to hear colleagues say “Claude said…“, “Claude thinks…“, “He implemented…” ) They are supremely helpful for me not remembering syntax, writing a module which would have taken me a two days in about an hour, and helping out after several readings of docstrings where I’m still not getting it! But I have also taken the time to read the Joy of Clojure and Programming Clojure and The Little Schemer with my own synapses firing :)

💪 1
Thomas McInnis 2026-04-05T01:35:16.201119Z

Also my personal machine is an LLM free zone, and I am writing a small little app achingly slowly the old fashioned way

seancorfield 2026-04-05T01:35:31.433999Z

I've spent the past two days driving GH Copilot to add a completely new billing provider to our system at work so I would put myself in the pro-LLM camp at this point.

seancorfield 2026-04-05T01:37:01.365419Z

(but I've been doing Clojure in production for over 15 years at this point)

Thomas McInnis 2026-04-05T01:40:05.236249Z

This is contributing to the feeling of being ‘naked’ - I’m only 4 years in tech (career changer into FE dev), and at the end of the day it’s my name on the commits. I am fully pro-LLM when you know the domain and the stack, but I obviously don’t. So I’m sweating everything.

seancorfield 2026-04-05T01:42:51.737249Z

It's gotten shockingly good recently. I was quite the skeptic before but I'm fairly all-in now. Microsoft's Agent-Forge has been really helpful at creating instructions and skills in repos I work on, so now I can delegate a lot of "grunt work" to Copilot and multiple agents...

Krishnansh Agarwal 2026-04-05T04:26:56.502559Z

Folks, can someone share a free resource to get started with Clojure (preferably any official one provided by the core team itself) which is hands-on oriented & modern / up-to-date

daveliepmann 2026-04-05T07:32:27.672869Z

Have you read the various https://clojure.org/guides/getting_started (on the left nav)?

Krishnansh Agarwal 2026-04-05T07:35:58.371489Z

Okay just checked, I was unaware Thanks 🙏

👍 1
danieroux 2026-04-05T11:39:40.145119Z

https://clojure.org/news/2026/03/30/zero_to_repl is also in the conversation

seancorfield 2026-04-05T13:47:53.085939Z

Perhaps also look at https://clojure-doc.org/articles/tutorials/getting_started/ -- not "official" but in 2023 it went through a major overhaul and modernization, funded by Clojurists Together, and it has since been updated to Clojure 1.12 in key areas. It includes step-by-step tutorials for writing a small web app, and for creating and deploying a small library, amongst other things.

👍 1