In 2017 a customer wanted a set of services written in Clojure. Based on their Java experience they wanted to hire 10 devs. When I arrived they had 4, over the 1.5-year period I was there, they hired and fired 4. When I left they had 3. All 3 were not experienced in Clojure prior to the project (Java), but by 2018 each knew the entire codebase and was able to modify it and able to step in for each other. They spent less than half of their budget and all goals were met ahead of schedule.
IME learning how to put Clojure and its ecosystem in good use takes more time and effort than other languages, but its effectiveness really compensates.
If I could make my own language and stack, I too would be mega-productive. However, others likely wouldn't like it and be confused by it, even though it's dirt obvious to me. Roll-your-own has limits. Sometimes 2 or 3 like-minded Lisp developers connect and roll for a while, but over time they will find it a lucky match-up that the real-world can't repeatedly recreate. Lightning in a bottle evaporates easily.
The bottleneck of "paycheck" programming productivity is communicating between human beings, who may shift over time. Cranking out compact code fast is secondary to this. Program for people, not machines and not cleverness graders.
Cyc is developed by a team since the early 80s. Reduce since the mid 70s. The SBCL and CMUCL Lisp implementations come out of the early 80s. Maxima has its roots in the late 60s/early 70s. The commercial Lisp systems from Franz and LispWorks are developed since the mid/end 80s. ACL2 (a theorem prover used in the chip business) comes from the early 90s. ITA (now Google) started mid 90s. GNU Emacs is from the early 80s. Autocd/Autolisp comes from the early 80s. PTC's Lisp-based CAD system (with a few million lines of Lisp code) must have been originally 2000 or earlier...
There are also a bunch of examples where Lisp was used to prototype or for rapid application development. This enables exploring ideas or conquering early markets. Later iterations may then reimplement the thing... That's another model and is also fine - though there are projects which had not much experience and were not able to get the thing off the ground. For example the first version of the Postgres database was written in a mix of some Lisp implementation and C. This did not work well for them and they switched early away from Lisp. Similar the first version of Reddit was written in Lisp, but it did not really work that well for them and they switched to Python.
> The implication often given is general purpose productivity superiority
I don't think that claim makes sense in general. Much of the productivity in modern software development does not come from the programming language. Much more important nowadays is the general eco-system.
For example it makes very little sense to develop apps for iOS in Clojure, since there is zero support for it from Apple. Thus most iOS applications are written in Objective-C and/or Swift. An assumed 'general productivity superiority' doesn't help at all to compensate the total lack of platform support from the platform provider.
I meant for "general" programming. I apologize for not wording it clearer earlier.
Re: There are also a bunch of examples where Lisp was used to prototype or for rapid application development.
Yes, this is a niche it often does well in. It's partly how Paul Graham beat competitors in the store app arena.
The post you responded to specifically used Java as an example, though. Clojure specifically is clearly targeted as an alternative to writing raw Java. A number of other languages rushed in to compete for that space, and while Scala and Kotlin seem to be more popular than Clojure, I have to admit the only JVM app I actually rely on now, uses Clojure.
Puppetdb, specifically. Puppet is a ruby project, but the database component is written in Clojure. I dislike Puppet for many reasons (syntax, coupling, architecture), but not because of its database backend.
Their code was never touched by those who stayed and were able to work together. Later it was rewritten, because in spite of tons of unit tests (which passed), it was full of bugs. The code was considered "write-only". The rewrites averaged at 5-10% LOC.
I agree there's a sometimes frustrating lack of branding and documentation for the one true way of doing things
But I'd be careful what you wish for, Clojure is still fertile land, nothing has come along and crushed all alternatives yet and I think our community is right to think long and hard before promoting the new one true way because most of us are refugees from other languages where it's already happened
On the other hand, to start with just picking something reasonably mainstream (in Clojure terms) is a perfectly good strategy. The choice will be poorly informed, but that's a given if you're a beginner in any case. And such a beginner isn't really in a place anyway to make this choice for a significant project (for any platform/language).
Once an initial choice is made, it seems to me (so far) pretty easy with leinigen templates & plugins just to get started on something. Time from tool setup to coding hasn't been any longer for me than other things I've learned in recent years.
The leaky abstractions were the other painful part – Clojure itself wasn't so bad, but Clojurescript (which would sidestep the startup issue) always seemed to leave me in callback hell.
Ultimately rust (and go to some extent) seemed to fit my use cases better.
If I had to do a web app from scratch I would think long and hard about going Clojure if only because it leverages such a widely used VM. Points 1 & 2 weren't issues for me, but #3 was (and there are similar problems with Elixir).
Building CLI tools in C/C++ is silly. If you like Rust/Go, sure, knock yourself out.
But the obvious choices are the scripting languages with runtimes that are already distributed as part of the OS .On Linux most CLI tools are built in Python or Perl and Ruby or Node.js are decent choices too.
JVM languages aren't an ideal choice due to the startup time, but given the size of the ecosystem you can find a library for pretty much anything you need and now we've got GraalVM to help with the startup time. So not a bad choice either.
Idk - If I wrote a tool in any of those languages and used state-of-the-art features then you would not be able to use them with the system interpreter and you’re back to square one.
I made a couple of simple command line programs at my previous workplace and the functional style of Clojure combined with lazy sequences made processing an unknown amount of resources on our server a breeze.
I would think that the language of choice for a command line program would have more to do with the task you need the program for and not the command line format itself.
Obviously for performance critical stuff such as implementing something like ffmpeg you would want to use rust to squeeze out every last bit of performance.
1. you get all JVM libraries (eg JDBC) 2. it's very compact to write and maintain 3. you can develop stuff using a REPL, and this is very handy for ill-defined problems. 4. if the result is worth it, I can create a Graal native for it (but I hardly ever use this)
I ended up as an author of a thing to minimize the boilerplate involved: https://github.com/l3nz/cli-matic
- Babashka: https://github.com/borkdude/babashka
- Clj-kondo: https://github.com/borkdude/clj-kondo
- Sci: https://github.com/borkdude/sci
- Jet: https://github.com/borkdude/jet
But again, those are tools (especially babashka) from a Clojurist's perspective
End result is 8.1MB for "hello world".
In ClojureScript you can use core.async to write async code in straight-line form. But I find myself using it pretty seldom, as handling events explicitly in functions feels pretty natural in Clojure especially given how naturally closures work in the language.
Case in point the var/agent/ref system is fantastic and probably critical to how to think about Clojure projects - but the problems being solved by it are a long way away from the concerns of someone at the beginner-intermediate type level. They don't need to solve multithreading right now. They don't see their data model as an urgent priority.
Compare that to Python where most of the core language is (1) call function, solve problem or (2) feature does something you could already do but with a little less typing.
I prefer Clojure but it is easy to see that the lack of an opinionated onboarding process is going to hurt the language.
But tools-deps don't care about packaging, deploy, install, etc. Like its name, it's focusing on `deps`.
I use tools-deps and shadow-cljs.
Having leiningen not bundled into the clojure distribution, is in strong contrast to the "batteries included" approach of python.
There were also complexities in including local jars into projects, as it required setting up a local maven repository, which is tedious job.
Also when exeptions occur clojure does not give an interactive repl like other lisps.
The problem is that Cognitect (the company behind Clojure) don’t use Leiningen themselves, and instead have released their own tool called deps.edn.
This has only fragmented the community even further, making it even more difficult for newcomers, with questionable benefits.
First, it is not called deps.edn, it is called tools.deps.
Second, it is without a doubt, 100% untrue that projects that Cognitect employees work on, exclusively use tools.deps. Cognitect has client projects they start from 0, and existing projects they shepherd and grow responsibly. Their first priority is to help their clients succeed in their goals, not change the build tool. I have worked with them, and the attitude was always pragmatic > dogmatic.
Where I will agree with you is that some folks in the community took an alpha release of tools.deps and treated it like it was the new lein or boot. It isn’t. It’s different, and as a Clojure programmer of 10+ years I use it in exactly 0 production projects. That said, it improves greatly upon “lein checkouts” by allowing one to refer to git SHAs as versions and do cross-project interactive development.
Like I mentioned Windows tooling and distribution seems not get much focus.
Emacs, cider, nrepl workflow on Linux is too complex with many moving parts, and very brittle(may be things have improved recently).
A major pain point for me specifically is that on an exception you don't get a live repl, you only get the stack trace of a dead program.
Even some minor inconsistencies lambda positional argument numbers seem to start from 1, than 0, which is where indexes start in all other cases.
Anyway, I'm mostly concerned by the fragmentation of it all. It doesn't help that there are now three (or four if you include maven) package / build tools for Clojure. The community is barely large enough to support a single one.
If I want to start a Python project, I `pip install -r requirements.txt` and I can start using it. In Clojure, I need to choose which tool I want to use to perform this simple task, in Python this is standardized. Leiningen, Boot, tools.deps and Maven are all candidates to do this.
1) compatible 2) if you're a newcomer, you probably only need a few dependencies, which is configured in the same way as lein's `project.clj`
I mean, I agree that there definitely is a need for better guides for Clojure and defaults, but I don't think that is the reason Clojure is not more popular, and that Python is simpler there. If I encounter Python for the first time, I definitely won't know that I just "pip whatever".
I actually think that Clojure is very popular, given that there is no big Co that pushes it, and that it still doesn't have a widely known killer app. Most other popular(ish) languages are mainly popular due to huge push by a single big corporate sponsor.
It currently have alpha support for Windows. They are still ironing out the quirks: https://github.com/clojure/tools.deps.alpha/wiki/clj-on-Wind...
You no longer need local Maven repo, because tools.deps supports local dependencies, you can basically just depend on local folders. It also can depend on git repos.
If you look at Python, Pip appeared in 2011, where as Python launched in 1991. And if you look at the Python scene, it's pretty messy as well, just read this guide: https://packaging.python.org/guides/tool-recommendations/
Pip isn't even the recommended one to use anymore, slowly being superseded by Pipenv, yet you also have Poetry and conda and to package things you still need setup tools and you have to use twine to submit wheels to pypi, etc. And I still see a lot of guides telling you to use easyinstall still.
In Clojure, you had Maven first as the de facto, then Leiningen became de facto, and now you have an official one with tools.deps as the new de facto.
For what it's worth, I am a system administrator (or DevOps engineer or infrastructure architect or whatever the trendy name is now). I've written several non-trivial automation tools where I had the option to use pretty much any language I wanted. The startup time and overall awkwardness of deploying the JVM for scripting was the undeniable deal-breaker. (I used Python instead).
> For boring code, which is the reality for many developers, they just want a clear best practice to get the stuff done.
More likely they just need a solution with the least-necessary added complexity or observable downsides, and that rules out the bulky JVM with its slow start-up time and the fact that Java has been under the jurisdiction of Mordor since 2010.
I think Clojure’s sweet spot is a small team working on a large enterprise project.
If you want to get started, Clojurists will insist you learn emacs. After all it's so great you must learn it.
Now I have nothing against emacs.
But I don't expect to have to invest significant effort to learn a specific editor in order to use a programming language. No other programming language has this requirement.
I was surprised at how many Clojure users took this as an attack upon emacs. It took some careful explaining to make the point that I have better things to invest my time into, such as building more code in Clojure, rather than learning yet another tool, that does something for which I have other tools already.
There were some great IDEs for Clojure. But they have all fallen into disrepair and being unmaintained.
Not sure where you get the disrepair part, Cursive is alive and well and seems to be a sustainable business for the author, and Calva also seems to do be doing well.
It's true that there have been various open source IDE efforts that are dead now (Counterclockwise for Eclipse went the way of Eclipse, Lighttable and Nightcode were probably a bit too experimental / radical)
I did use Eclipse and Counterclockwise for quite a while and enjoyed it very much.
It's on the wrong side of history in that respect.
I know it's trying very hard to catch up to statically typed languages now by retrofitting some type system, but it's too little, too late.
Static types are where the current state of the art is, and we're not going back. Clojure missed that train and will never catch it now.
Types solve 2% of programming errors. They lead to coupling for the sake of the compiler, and make code harder to change, harder to write, and often needlessly constrain the utility of the code where they're applied.
But they do make the IDE code-completion go, so there's that.
They also make code easier to reason about since the coupling that exists because of types is the result of an explicit and thoughtful decision on the part of the author. You don't as often get into situations where the code appears to work based on superficial similarity of data, only to find out there is some deep difference which prevents it from ever working.
It's also the truth that the more expressive the type system is, the less coupling you are required to do. For instance, if the language supports type classes (traits), you can codify the fact that a part of the system requires not data of a particular type, but of any type supporting some set of operations. This is the very thing you imply is lost when coming from dynamic types, only this time it's tool-backed and not merely accidental.
When you get a totally unfamiliar code base, only if you can run it over and over, or at least run the tests over and over, then you can talk about the refactoring. If you can't, the refactoring is almost impossible, not matter how strong the type system is.
For an argument which require type of string, the behavior could be unexpected if you just give it a string.
Very often it feels like they're "elegantly" trying to solve some made-up, bullshit problems for some questionable gain. Feels like bureaucracy for the sake of bureaucracy.
Totally like OOP shit that we're still trying to make sense of, like:
public abstract class Ellipse2D extends RectangularShape
What's the problem? Ellipses are rectangles with some extreme rounded corners. That's exactly how Euclid described them.
That being said, there's no conclusive evidence that dynamically typed systems can't be robust and scalable. Sometimes, dynamically typed systems make absolute sense, especially in the context of homoiconic language like Clojure, where you have a "true" REPL.
An analogy I can think of is wired headphones vs. Bluetooth headphones. Audiophiles would vehemently argue that you cannot deliver quality sound via wireless, and all professionals use wired headphones. But sometimes, wireless headphones are what you need - they grant you some freedom, for a small price - you have to charge them, you have to be close the source all the time, there's interference, etc. But at the end of the day, I rather charge them once in a while and enjoy the music.
After using Clojure for some time, now, whenever I need to program in a statically typed language - it feels like I'm a traveler, passing through a series of checkpoints in medieval China or something. Too much ceremony. Perhaps I'm just not smart enough to solve puzzles imposed by a type system over and over again. Maybe the simplicity that Clojure offers allows me to stay dumb and focused on the task at hand, and enjoy the ride.
That was never the claim, though. It's possible to write anything in any language, witness the millions of lines of code that are written in PHP or FORTRAN today.
The question is trying to determine if there are characteristics of programming languages (such as their type system) that make achieving these goals easier, and which also possess other nice attributes (such as making the code easier to refactor and maintain, easier to navigate or learn by new hires, etc...).
In my experience, dynamically typed systems are harder to refactor, harder to understand, require more cognitive load to understand them, and are typically slower than statically typed languages. And because of the absence of type annotations and the 100% reliance on the (hoped) existence of tests, many developers simply decide not to refactor dynamic code for fear of breaking it, which leads to much more pronounced code rot with dynamically typed languages.
> Maybe the simplicity that Clojure offers allows me to stay dumb and focused on the task at hand, and enjoy the ride.
I'd argue the opposite: dynamically typed languages require you to hold a lot more stuff in your head (the types and what each object is and what they can do) whereas type annotations allow you to focus on more important things.
The flaw in any argumentation about programming languages is almost always universally stems from the fact that we eagerly paint everything either white or black.
And I've been coding for long enough to learn that there are no universal answers - clean OOP, or pure FP, dynamically or statically typed, garbage collected or manual memory management, etc. The answer is almost always: "it depends".
Looking at any specific language through a prism of your own beliefs guaranteed to form opinions that would be flawed.
You can't put all dynamically typed languages into the same bag - programming in Python is vastly different from programming in Clojure. Same way as you cannot do it for other properties of the language, like it being a Lisp or being hosted on JVM.
Clear, organized, well-architected code, in any language, is the skill that takes a long time to develop. I do not personally find clojure makes this any easier than in any language.
Surprisingly, it does for me. After using many different languages, I find myself more productive in Clojure than in any other language I have used before. Every language I used before Clojure left a dent in my mental ability to appreciate what I do. It's not a single favorite PL of mine, but most other programming languages make me feel bored and not interested after a year or so of using them. They have so many inconsistencies and quirks that you slowly succumb to the inevitable - you become a hostage. Your language of choice becomes your mental prison. Your hobby becomes this thing where you dig up yet some another poorly documented inconsistency and brag about it to your colleagues and friends. You become an expert from burning too often and too much. The language becomes your identity because you've seen too many ugly parts of it.
You have no idea how many times I had to fight my anxiety and depression and seriously thought about leaving the field for good.
Learning Clojure has liberated me; it allowed me to maintain focus and put in use all the good things and patterns I learned over the years. In other languages, sometimes you have to bend over backwards to create something clean. Sometimes you have to build this colossal cathedral that requires enormous scaffolding just to hold its own weight, and you can't even remove the scaffolding, and you call that "an elegant solution."
It means you can avoid getting into the situation where half your logic is encoded in the type system and enforced at compile time, and the other half as values at run-time. It's all values at run-time.
And for the same reason, you don't fall into the trap of "puzzle-solving" with a type system. Between run-time values and the compiler there is a world of infinite possibilities that some type systems (and I would also include some macro systems!) seem to encourage adding more and more layers to. Clojure tries to speak the language of data, which is a lot more grounded.
We can develop our program while it's running.
And it's not a tricky thing that injects or restart anything, it's by design. Load file to REPL and now your functions are redefined. Just on this namespace. Load file/reload isn't a automagical thing. It's simple: just "stream" your file to the REPL. HTTP library, DB library, any library need to thing about "how do I hot reload", it's a language feature.
I see many developers arguing things like "types are important because avoid runtime errors". If you develop INSIDE runtime, you have no reason to fear runtime errors. Your runtime error will blow up during development process.
A cool thing about this pieces:
- I can start my app from REPL
- Connect my browser in this app
- Run my integration tests that create entities (inside the same REPL)
- See in the browser the state from app after run the test.
However, someone might turn around and say, “well, I can pull in a lib with immutable data structures in $lang if I want to.”
It needs to be highlighted that the real game changer is an /ecosystem of libraries/ built entirely out of immutable data structures.
That’s not something that can be engineered as needed.
Plus Clojure Core Teams laser focus on API stability makes 10 year old (and even unmaintained) libraries to just work. I have not come across a code snippet so far that did not work because of a breaking change, not saying they are not there, but I did not find it.
Yes, there are other Lisps, but Clojure also improved certain things compared to them:
- Clojure has way more reach. Being that it runs on the JVM, JS, CLR and has great interop. It means you can actually use Clojure instead of Java, JS and C# to do just about anything you could with those. That's not true of Common Lisp, Scheme, Racket, ELisp, where the main runtimes don't have a lot of money behind them, and where libraries trail behind.
- It disallows reader macros, so there's a limit to how wild people can customize it. This helps minimize the Lisp curse.
- It also has a pretty simple macro system, that is both hygienic, yet straightforward to use.
- It has a more visually pleasing syntax, by extending homoiconicity to also support maps, vectors, sets, regexes and keywords.
- It modernized some old remnants, like having first/rest instead of car/cdr.
- It ditched cons cells, and instead uses a proper sequence abstraction.
- It has proper true/false, and nil is not the same as the empty list.
On top of being a great modern and improved Lisp with bigger reach, it also is just a well designed language. Lets explore some of that:
- Functional programming as the default paradigm, but others are supported (logic, OOP, imperative) when it makes sense.
By default Clojure uses immutable persistent (fast and memory efficient) data-structures and variables are immutable. Functions support full closure over their environment. Anonymous functions are first class. Higher-order functions are included. Loops are handled recursively. The whole shebang. Yet, you can relax this in controlled way when it make sense. You can introduce controlled mutability, you can define polymorphic functions, you can create mutable types, etc.
- Every collection under the sun.
Data-structures are fundamental to computer-science, and Clojure has a bunch of them. Persistent Lists, Vectors, Maps, Sets, Queues. LinkedList, HashMap, ArrayMap, Array, DoublyLinkedList, HashSet, TreeSet, ArrayList, PriorityQueue, TreeMap, etc. All built on proper abstractions as well: Associative, Sequential, etc. And it has a ton of useful functions to operate over them as well.
- Awesome data manipulation constructs
Some people say information systems is all about taking information and transforming/moving it around. Boy does Clojure has you covered there. It has an awesome set of performant lazy data manipulation functions/abstractions called lazy sequences with things like: map, filter, remove, distinct, dedupe, group-by, sort-by, partition, split-at, replace, shuffle, reverse, rand-nth, etc. And, all of these are also available in an eager variant as well which performs loop fusion (called transducers).
- Great equality semantics
In Clojure, equal values are equal things. This is just awesome! Like, that's how a layman thinks of equality, and that's how programming languages should have it as well in my opinion. For performance, you can decide to use reference equality as well, but that's not the default.
- Full support for concurrency and multi-threading
Kind of self-explanatory, but Clojure has a lot of concurrency constructs which make it easy to write concurrent/multi-threaded programs.
- Types are open for extension and have good polymorphic support
A bit like traits and mix-ins, types can all be extended from outside their definitions, and polymorphism exists at many levels: dispatch based on the type of the first arg, dispatch based on the value or type of any arguments, dispatch based on the arity, dispatch based on some hierarchy, etc.
There's more obviously, but this is already pretty long. So I'll finish by answering your last question:
> Do you have any ideas what any other language (e.g. Python, TypeScript, Go, Rust, Julia, Haskell, OCaml) would need to change to make you as productive as Clojure?
Everything. I mean, they'd just need to become Clojure. The thing is, see how long my answer is? That's because it is not just one "killer feature" that makes Clojure awesome. Clojure has just the right balance of features all designed in just the right way to come together beautifully and coherently to create something that is greater than its parts. That said, you can have a look at Elixir, I think it gets closest to providing something that approximates Clojure.
Error messages could be improved, currently, they often leak the implementation details, so the error is disconnected from your actual code.
Startup time is slow. There's ways around, like making a Graal Native build, or using ClojureScript or babashka instead, but those are all alternative thing you need to consciously choose to use. It be great if standard Clojure somehow could start really fast.
Memory usage could probably be improved further. It makes liberal use of Objects right now for everything, and that adds up quickly.
Performance is pretty fast, but I'll never say no to something that would perform faster.
I think I would make the data manipulation functions eager by default, and the lazy one would be the opt-in one.
There's a few names that could be improved, contains? is a famous confusing one, since it always checks for key, would have been better to call it contains-key?
When it comes to the language design itself, I'm not sure there's much I'd change. I think it could be interesting to try and see if you could build some language that's like Clojure and have some level of static type safety. I'm not sure what you'd have to trade away for it, but I think it be an interesting experiment.
Oh, and one more thing, doing unboxed things, operating over primitives could be improved and made easier. This is often needed for high performance numerics, or making better use of memory and caches.
I can't tell if I happen to have lucked out for the first time ever working with really good engineers, and that's why the Clojure code bases I currently work on are overall better. Or if it has anything to do with Clojure itself.
From my prior work experience: Scala, Java, C#, JavaScript, ActionScript 3, C++; the code bases were always kind of crap. Everything was always called "legacy code", even if it was something that we had built just the year before.
Similarly, in the open source, or even language core, things were always deprecating one after another, new release introducing breaking changes, and you had to constantly play the upgrade and refactor game to keep things working. That in turn contributed to making our own code bases so called "legacy", as the framework used even a few months back is being deprecated, or libraries you depend on that you can't upgrade to the newest release without breaking everything so you stick to outdated dependencies.
None of that happens in Clojure, things are mostly stable for decades.
For me, Clojure definitely made me way more productive. My background was in statically typed language mostly prior, though I had done some Python and JavaScript as well. With Clojure I feel I'm between 30% to 300% more productive overall. I think some of this is personal, depends on your style and your own strengths and weaknesses.
Typescript can be annoying but usually it helps a lot with refactoring. It's also pretty great for detecting unused props with react.
I don't use IDEs so my defense of typescript is only based on its type checking merits
Edit: Googled and nope, can't find anything.
https://dev.to/danlebrero/the-broken-promise-of-static-typin...
https://www.i-programmer.info/news/98-languages/11184-which-...
https://nextjournal.com/PRL-PRG/toplas-analysis
One thing to note is that the absolute difference in terms of bugs from the worst language to the best is still minimal. So language choice doesn't seem to make or break software.
Another thing to note is that while overall static languages faired better in terms of having lower defects, Clojure did best of all.
I knew of another one but can't find it again.
Then there are a few where they had beginner programmers implement trivial programs in different programming language and checked how many errors/time it took them. But I consider those all pretty bad since the experiments are so reductionist. So I won't list them. They aren't conclusive either. Some end up saying static and dynamic are same, some say dynamic is more productive at equal defect, and some say static had less defects at equal productivity.
Vastly understated in my experience. Even trivial python programs contain errors that could be prevented with a trivial type system like Go's
And performance. Not being fast enough is a bug. Consuming much more energy is a bug, in my book.
Sufficiently expressive type systems are better than dynamic typing. They prevent so many errors, especially in FP-inspired high level data transformation functions. I am saying this as a person who doesn't even like most of FP. (You can infer from my comment history).
or Python's. It even has generics.
That said, I still like some dynamic languages for certain things.
I like static typing and agree that it will eventually "win", but I really don't see where you're coming from here.
> I know it's trying very hard to catch up to statically typed languages now by retrofitting some type system
What makes you say that?
Even just parroting some of the memes from this thread like "static types only prevent 2% of bugs" and "you can replace 12 developers with 3 amazing (italics) Clojure developers" and "a Clojure developer can replace any kind of developer!" means you don't have to make up absurd viewpoints to argue the contrary. These trends are old as time and completely nonsensical.
I think dynamic languages are generally a good default, but the current world also matches them pretty well because programs talk to the outside world in a lot more varied ways than before and static typing is not that very good across distributed systems.
Static has a better case when talking about more powerful type systems than what mainstream languages have, but those don't really show accelerating signs of breaking through to mainstream. Rust is an exception in the recent history but it's not really poised to become a widely applicable app programming language.
I'm seeing Clojure related posts on top of HN almost every week. And I'm watching people trying Clojure, coming with all sorts of different backgrounds. I have worked with people who came to Clojure from C++, Ruby, Python, C#, Haskell, Go, Scala, OCaml, JavaScript, CL; people with no programming experience at all.
Of course, that all is incomparable with the craze that something like Typescript receives. And I used to feel anxious, worried that this would be the last ever job where I happily used Clojure. But after my third job where Clojure was the primary language, I stopped sweating about that.
I think Clojure, at this point, has stabilized in the industry and slowly and steadily would continue to attract people. The only thing that can genuinely kill Clojure - is a better version of its fork.
But as you say, it's popular "for a Lisp dialect" and I don't expect it to reach past that. I would love to be proven wrong.
That being said, your post made me realize that instead of lamenting the nicheness of Lisps, I should be encouraging other people to give Clojure a shot. And not only to help Clojure be more successful, but also to bring a little bit of joy to their daily life like Clojure often does. (It sounds trite but it's true.)
1. Tooling. You end up spending way more time getting your tooling setup than it should be. 2. Difficulty in learning. 3. Clear best practices and frameworks. The philosophy of composing libraries is extremely powerful, but to gain broader adoption you still need straight forward frameworks and opinionated best practices.
The last point has many unintuitive consequences. Clojure tends to attract people who enjoy doing new things, or excelling in their craft. But mass adoption also means catering to people who need to do "boring" things where people just want to get their job done. For boring code, which is the reality for many developers, they just want a clear best practice to get the stuff done.
This could be a clear standard library or framework for a task, a clear architectural pattern, and it certainly means easy to access code snippets. There have been some attempts at these things, e.g. Luminous for the web, but the community hasn't rallied around them with enough weight to make newcomers comfortable.
So when faced with choosing: a testing library, any number of libraries compose into a web service, any number of approaches to client side, then in the end still having 100 equally good ways to architect a given piece of code that requires thinking mathematically, well, it's daunting. Especially when you are new to the language and just want to get your boring CRUD app working.
Maybe this is best summed up as saying that Clojure caters to the creative, but to gain widespread adoption it also needs to cater to those stuck writing boring code and just want to be done with it as fast as possible.