I've seen plenty of tomatoes here on the orange site, thrown at Clojure.
Interestingly, years ago, when I was deciding if I should learn Clojure, I have typed into Google "why Clojure sucks". That's actually my usual "research technique" for any new tool - after the initial intro and the wikipedia pages, I typically do that and try to find some criticism, in order to remain level-headed. And guess what? To my surprise, instead, I found a plenty of compelling reasons to learn the damn thing, which I did. That has changed my life, not exaggeratingly, quite for real.
So my conclusion and advice to young programmers? Use whatever tools make you happy. Don't worry about their popularity, don't listen to couch-surfing "experts", try them for yourself. Be skeptical, first and foremost about your own reservations, thoughts, and feelings - something you dislike initially may change your perspective later. Conversely, be looking out for even better ways - remember, it's always possible to find renewed joy for the craft, even after decades of trying different things. There's always something out there that would feel like it was made for you to enjoy it.
The stacktrace-barf has improved a bit in recent versions as I recall, although I've lost the ability to see them by now.
SMH.
With the right discipline (specs, obsessively normalising all data at the boundaries, good naming conventions) this wouldn't have been a problem, but that discipline is optional, and headbanging aggravation results.
(This is, of course, a generic "dynamic typing" problem, but that's a key feature of Clojure)
Statically typed languages reduce the need to know how the data is structured or manipulated. The market has clearly chosen this benefit over what Clojure can provide.
But that's not what a Clojure dev would do.
1) We use Malli [0] (or similar) to check specs and coerce types if needed at every point. Checks can be left on in production (I do), or disable–up to you.
2) If the coercion is difficult, use something like Meander. [1]
3) If even that isn't straightforward and you need actual logic in the loop, use Specter. [2]
4) If you're not sure what going on at intermediate steps, use FlowStorm [3].
5) But you're going to be processing a lot of data you haven't seen before! Use, Malli with test.check [4] and make use of property-based testing with generators.
None of this is "advanced" Clojure, this is bread-and-butter stuff I use every day.
6) Need a Notebook-like experience to get better visualization of intermediate data? Use Clerk [5].
7) Need special checks on API usage within your codebase? Use clj-kondo [6] with custom linters. They're less than 10 lines each.
Unlike default-mutable languages, or typed, it's safe and easy to use libraries with Clojure and they tend to have very little churn. Total opposite from Python or JavaScript (if you're used to that).
It's almost impossible to give the impression of what it is like to develop with Clojure if you've only ever used languages with static typing, or languages from the Algol family.
Honestly, I hated Clojure's syntax at first BECAUSE I COULDN'T READ IT, and I loathed "structural editing." After 2-3 weeks, I read it just fine and it's hard to remember I ever couldn't do so. Now I like it, and structural editing makes it so easy to change your code, I couldn't live without it at this point.
Basically, all my "fears"/dislikes were unfounded—it was a skill issue on my part, not a problem with Clojure.
[0] https://github.com/metosin/malli
[1] https://github.com/noprompt/meander
[2] https://github.com/redplanetlabs/specter
[3] https://www.flow-storm.org/
But this long list of runtime libraries is definitely a downside of Clojure. It's people trying to grapple with things mostly solved with static typing where you can just write a(b(c())) and it fails before it hits your fancy yet-another-thing-to-learn Malli library in runtime.
They might be great libraries, but you're only seeing one side of the trade-off.
I learned Emacs with evil-mode, paredit, nrepl/cider, and Clojure in my early 20s and used them for six years, and I was pretty gung-ho about it like you. But eventually I started using static typed languages for work and decided that I couldn't go back. It's like trying to read Javascript after you've spent five years with Typescript. You just think "wow, I can't believe I did that for so long."
And I'm remembering times I've used paper and pencil to figure out how map is being transformed as it's passed through library code. I don't miss that.
The Value of Values: https://www.youtube.com/watch?v=-I-VpPMzG7c
Using types to model data is a terrible idea.
Apparently I and my fellow Clojure devs aren't real Clojure devs. Or perhaps you mean "true" clojure developers, or "good" clojure developers. (cf. https://en.wikipedia.org/wiki/No_true_Scotsman)
And even if we were Clojure devs we've inherited multiple big Clojure codebases that were apparently written by non-Clojure devs, and heavily refactoring is not on the to-do list.
With static typing, you are doing specification and optimization at the same time, which is maybe necessary because compilers and languages are not sufficiently smart but also because of this mix it complicates reasoning about correctness and performance. Also static typing introduces a whole universe of problems with itself. That's why we have reflection or stuff like memory inefficient IP address objects in Java:
For a simple IPv4 address normally representable using 4 bytes/ 32 bits Java uses 56 bytes. The reason for it is Inet4Address object takes 24 B and the InetAddressHolder object takes another 32 B. The InetAddressHolder can contain not only the address but also the address family and original hostname that was possibly resolved to the address.
For an IPv6 address normally representable using 16 bytes/ 128 bits Java uses 120 bytes. An Inet6Address contains the InetAddressHolder inherited from InetAddress and adds an Inet6AddressHolder that has additional information such as the scope of the address and a byte array containing the actual address. This is an interesting approach especially when compared to the implementation of UUID, which uses two longs for storing the 128 bits of data.
Java's approach is causing 15x overhead for IPv4 and 7.5x overhead for IPv6 which seems excessive. Is this just bad design or excessive faith in static typing combined with OOP?
But this is because JSON is an untyped data structure. (And btw, a flawed one...)
You would have this problem in any programming language.
The few folks I know using it are very happy with it, and are very experienced, and talented talented polyglots themselves.
For that reason I'd not rule it out, even if I'm not looking to make a jump or change, but I might try it out.
Common Lisp was the most mind-blowing language I ever touched, and it seems the creator of Clojure really filled the gap between the brilliant simplicity of Lisp as a language and the access to a mature ecosystem. Maybe I should try Clojure as my next language (I am enjoying Rust for now; Rust macros are also cool, but way more complicated)...
1) runs on the JVM, access to any Java or Python library within Clojure without wrappers
2) an immutable-by-default language with a standard library that takes advantage of it
3) the best out-of-the-box concurrency story of any language I know of
4) a very well-developed ecosystem for developer tooling and general project stuff
Common Lisp is very fun, but the stress level is definitely higher with it due to the mutability and the generally less well-designed APIs. Only lists are actually functional, concurrency is YOLO-tier, etc.
If you already like Common Lisp, ABCL (Armed Bear Common Lisp) is a mature fully ANSI Common Lisp-compliant implementation, that runs on the JVM and can instance or load Java classes or call Java methods without wrappers and in only one line of code.
> the best out-of-the-box concurrency story of any language I know of
Lots of concurrency models also available on Common Lisp, including async, channel-based, etc.
> Only lists are actually functional, concurrency is YOLO-tier, etc.
I'm sad you had that experience, but there is tons of solid concurrency support in Lisp, for example lparallel is awesome.
Clojure is nice as a JVM language alternative, that is, better than Kotlin, Groovy, etc.
However the loss of the great interactive development facilities that Common Lisp has is a major, major downside. Interactive development is what makes CL (and also Smalltalk/Pharo) great.
I disagree Common Lisp has a better development environment, I haven't found that to be true.
But use whatever makes you happy!
I have ADD and I once heard that devs with ADD/ADHD have an incredibly small heap size for context but compensate for their weakness by being great at solving logical problems in that small heap. Types have been essential for me when functioning in code bases. I really struggle with pure JS and untyped Python.
Clojure was similarly hard for me. What tools and/or techniques do such folks use for comprehending already written Clojure code?
Even if I have a smaller total heap size (maybe), personally my hyper focus allows me to nearly dedicate all the heap space to the specific task at hand. I probably outperform neurotypical people here. I just can't have anything else in my head. Task switching kills me.
So it is hard to say cause everyone is a bit different.
For me the interactive, REPL-based workflow makes my ADHD brain very happy. Always having a program running is really nice.
Plus immutability makes it much easier to reason about things.
I do like static typing as well and I could see how it might help. I strongly believe that gradual typing allows for the best of two worlds, so that you can do both exploratory, interactive programming and type driven programming, depending on your needs.
Not sure how well the solutions for gradual typing in Clojure work though. I have only experience with Common Lisp. Coalton might the exactly what you need: https://github.com/coalton-lang/coalton
In addition to that, a real REPL programming really helps to do small tests and understand the code quickly, immutable data structures with data-oriented approach and locally scoped code blocks combined with structural editor are godsend as well.
You mean dynamic typing, I understand.
Clojure (and Common Lisp) is strongly typed, so if you expect type A and you give a value of type B, an error will be raised.
On Common Lisp, which is an interactive development language, you just inspect the stack frame where the error was raised, find the problem, correct the code, recompile your function (while the code is running), and "restart" the stack frame, so the execution continues (without having to restart or redeploy everything and try to replicate the bug). Thus, it is no big deal at all.
On Clojure i'm not so sure how extensive is the interactive support. But there is "spec", which can help.
When you're writing software you benefit from libraries the community made, and lots of people end up writing libraries and contributing back. It's possible Zig is much better for C, but they're failing to gain users due to lots of C developers continuing to use C for new projects, whereas if there were more users maybe the Zig ecosystem would be larger, making it easier to write larger pieces of software.
And I think basically all software is a collaborative effort. Users have investment in tools they think are useful, and frequently contribute or even take over maintenance on critical software. In that case they need to deal with whatever technical decisions that project made. Even commercial software can be abandoned as a buggy mess due to technical choices forcing users to migrate, or eventually open sourced (blender, doom, etc) in which case private technical choices become public. Also I think a lot of people have been forced to contribute (i.e. professionally) to projects that initially made very poor technical decisions and have been scarred by it.
No tool is perfect, and abstract technical debate is awful, but I think it's hard to say there isn't worse and better software, and I think it's worth the effort to put in effort to debate to try to discern and move from worse to better tools since the whole community benefits that way.
Edit: re-essayed
And you know what? Finally, I realized - I don't have to explain to anyone in exact detail why I have not found the same deep love in C, C#, Python, Javascript, Typescript, Ruby, Go, Java, Kotlin, Swift, Lua, Haskell, and many others. Why do I need anyone's permission to love a tool? I love it, and I love it for many reasons - theoretical, practical, emotional, fiscal.
Sure, I can get behind your excitement for Rust, Kotlin, OCaml, Elixir, Julia - you name it, but please, please do not try to "educate" me about my choices. I don't care about YOUR personal predicaments with Clojure/Clojurescript/Babashka/nbb, even Fennel. You find Clojure not to be worthy of your time - it's YOUR loss. My love for Clojure is not due to MY skill issues, not the result of MY inexperience, not because "I'm in a bubble," or "I don't know any better," or have zero knowledge of type systems, category theory, OOP or design patterns.
Sure, Clojure is not without deficiencies - no tool is ever perfect. Yet pragmatically, no other programming language in the category of general-use PLs today satisfies me more than Clojure, no other language is nearly as joyful to use. I'm sure at some point my stance will change, I will find some other "perfect" language for me, and 100% guaranteed - it will too have some deficiencies and people will be arguing for the merits of choosing it for the job. Until that day, let me just say it again - "I fucking love Clojure!"