I love writing Clojure. Whenever I say that publicly, there are inevitably some voices challenging my stance with skepticism, criticism, and attempts to discredit whatever I say provides practical value for me. Then I have to explain to them, "no, it's not the only language I know," "yes, I've used dozens of other languages before," "yes, including languages with robust static type systems as well."
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!"
Through the last decade, I have found that programmers' judgment of technologies, tools, and languages are incredibly noisy. It is hard to make correct decisions based on opinions alone, and design by committee is a real thing.
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)...
If you already like Common Lisp, what Clojure brings are:
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.
> runs on the JVM, access to any Java or Python library within Clojure without wrappers
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.
Did you or any of your fellow devs have ADD or ADHD? How did they adapt to dynamic types?
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?
Not everyone with ADHD struggles with small heap size.
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
Reducing cognitive load is the key. Several approaches I usually take are:
1. Make strict rules for code convention, especially naming things, and stick to that.
2. Use intermediate variables (let binding) often
3. Turn meaningful code block to a function often
4. Write Clojure specs and turn them to docstrings
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.
Do you have any resources showing what this type of coding works in the real world? I keep running into this same types issue when I use languages without types specified everywhere, where the cognitive load gets too much for non-trivial projects. I would be pretty unhappy writing python without type hints and tools that check these for correctness built into my workflow for example.
This was from my experience that I thought may work for others too.
Types may help in some sense, but is not enough and sometimes even add extra load especially on heterogenous data structures.
Before using any tools beside basic IDE features, I think there are fundamental things you can do to reduce cognitive load, such as focusing more on code design, convention, structures, naming, testing, documentation, which can be applied whatever language you use.
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.
So I think people probably hear "I love clojure" and take it as advocacy. I mean, maybe it isn't supposed to be advocacy, but "I keep hearing people say they love X" is a decent reason to start using something, and I think people are influenced that way.
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.
The dynamic typing and "everything is a map" can be a PITA. At the moment I'm working on a codebase that has I/O to JSON APIs, Avro schemas and postgres databases. That means that a field called "date" can be either a string, integer days since the epoch or a Java Date, and (because this codebase isn't great) there's no way of knowing without tracing the call stack.
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)
>That means that a field called "date" can be either a string, integer days since the epoch or a Java Date, and (because this codebase isn't great) there's no way of knowing without tracing the call stack.
But this is because JSON is an untyped data structure. (And btw, a flawed one...)
You would have this problem in any programming language.
In other languages you would know which date you were dealing with based on the type regardless of the function you were in. In Clojure, you have to either name the variable date-string or find which API it came from, which means tracing the call stack
I'd emphasize that it's a problem with your particular code base. If you set it up correctly, all dates are properly parsed at the boundaries and you would only deal with one type of date inside your app. I'm working on a large Clojure app with a lot of date handling and never had any issues. For me, a date is always juxt/tick date.
The parent comment illustrates the problem with one clear example. In real-world code functions pass around amorphous maps, they add, subtract and transform fields. There is no way to know what's being passed around without reading the source of the whole chain.
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.
Yes and no. Statically typed languages only know that data stored in some piece of memory was conforming to some kind of shape/ interface when it was first stored there. That's why tricks like SIMD Within A Register (SWAR) work at all. E.g. when you need to parse temperatures from string input very fast like in the 1BRC:
https://questdb.com/blog/billion-row-challenge-step-by-step/
How does your type system help there?
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?
> There is no way to know what's being passed around without reading the source of the whole chain.
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.
Most people don't use those libraries, nor do most libraries use those libraries. They don't help me understand most code out there beyond my carefully orchestrated app code. I'm back to reading the source.
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.
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.
> I've never seen anything but praise for clojure.
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.
You've never seen someone encounter Clojure stacktrace-barf when something breaks before. The language isn't perfect. Although it is one of the few I've encountered that does immutability properly and that is great.
The stacktrace-barf has improved a bit in recent versions as I recall, although I've lost the ability to see them by now.
After writing Clojure/CLJS for a decade professionally I think it's a toy language that is completely inappropriate for industrial use. But, at this point it's not worth my time to talk about it past that
I was gonna reply something similar too. The only "counters" people have when talking about Clojure (that I see, as a non-Clojurian) are pretty weak, and are usually: "it uses JVM, and JVM bad mmmkay" and the typical lazy dismissal of Lisps as "omg it's parantheses everywhere".
The use of prejudice type behaviour towards others using a particular programming language based on hearsay or unknown is not ok, but also not something new.
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.
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!"