Hacker News new | past | comments | ask | show | jobs | submit login
Clojure: A Lisp that wants to spread (simongray.github.io)
386 points by simonpure on March 1, 2020 | hide | past | favorite | 291 comments

Startup time isn't what is holding the language back. In my opinion it's:

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.

Instead of just measuring how many developers adopt Clojure and how difficult/easy that is, I'd also love a measure of how many developers Clojure "puts out of business". IME Clojure is quite aggressive on that front.

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.

I've heard this "mass productivity" claim from Lisp fans multiple times before, and am skeptical. Lisp has been around for about 60-ish years, yet no software house has been able to conquer the software market using it. It doesn't seem to scale to bigger teams and long-term maintenance. Developers can easily create abstractions that fit their own head, but not necessarily other heads. They blame the problems on the other readers rather than their spaghetti mind map.

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.

Actually quite a bunch of 20 year or older Lisp software is developed in teams.

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...

I don't dispute it has niches where it does well, or at least "works". The implication often given is general purpose productivity superiority.

Well, you claimed that Lisp programmers can not work in software teams over a long period of time, because they don't understand each others code. I gave a few examples.

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.

Re: you claimed that Lisp programmers can not work in software teams over a long period

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 implication often given is general purpose productivity superiority.

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.

Were they fired because they weren’t good enough at Clojure? Perhaps all Clojure did was filter out less capable performers. The small team of skilled developers might have been just as fast in Java if they weren’t encumbered by the mediocre ones.

I was mainly concerned with teaching Clojure in this project as everyone had little or no experience prior, so nobody was blocked by learners. Those who were fired all had one thing in common: They refused to adopt Clojure beyond its syntax in favor of patterns they learned to rely on in another language. This varied from porting Scala polymorphism to just banging out imperative while loops with atoms. I don't think they were incapable. They simply didn't want to learn it. Code reviews turned into arguments quickly, took endless time and lead nowhere.

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.

That’s still an argument in favor of Clojure. People spend loads of time and money trying to figure out how to do such filters. If it comes for free by choosing Clojure, that’s pretty great.

I doubt this filtering is in any way done by Clojure though. It's difficult to recognize top performers in interviews, but it's much easier to see that in day to day work. What is usually missing is the willingness to fire people for not being top performers.

If all you want is a rails thing but with a Clojure twist I'd keep an eye on https://youtu.be/jkx9F-RIFiY

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

The normal one true way for most commercial companies right now is reframe, there's a nice ecosystem there and it's teachable

reframe is just the front end though isnt it? i think this is not the crud/rails alternative the op is talking about

As someone just playing with Clojure for curiosity, I agree that it's hard to find a way through the thicket of available tooling & library options.

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.

What got me started with Clojure was a coworker at megacorp. He's since moved on to a company that makes widespread use of Clojure in production. Startup time was a huge factor for me because I wanted to write command line tools with it.

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).

Clojure seems like an odd choice for command line tools. I can see why a committed Clojure programmer would want to reuse their existing skills for a slightly mismatching domain. But Rust/Go (even C) would seem like a more obvious choice if you weren't setting off from Clojure as a starting point.

A command-line tool is nothing more than a script that maybe parses some input from the program's arguments or from stdin and then does something useful. Naturally you want to have the libraries you're used to available and the best language for CLI tools is the one you already know.

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.

That... just ain't so. So many of the GNU programs written for the command line are in C, are performance sensitive and would be crippled by any of the scripting languages you've mentioned. You don't have to look far: grep, sort, sed, find, base64, tar, ...

>> Python or Perl and Ruby or Node.js

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.

Clojure's extensive support for lazy evaluation and sequences actually makes it decent for command line tools that process streams of data. At least in terms of program architecture and elegance. The biggest mismatch used to be startup time, but with GraalVM you can get very good startup times.

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.

yep, borkdude has been playing with calling Rust from Clojure too via Java JNA, so if most of your application doesn't need rust like performance but one specific part of it does then you're not dead for options

Small correction: it's JNI. JNA doesn't work with Graal as far as I know right now.

I use it extensively for automating tasks, and it's pretty good. Sure, it's not as snappy as a Go executable, but:

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

You should check Michiel Borkent's work (borkdude):

- 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


Nowadays you can use GraalVM to have quick startup time for commandline tools with Clojure.

KNow of any good tutorials for how to use Graal with Clojure?

Thanks! Took a while, but I got this working on Linux. Gave up on Windows, like with everything it never seems to work on Windows.

End result is 8.1MB for "hello world".

Windows can run Linux binaries now :)

Is Rust better in the browser wrt "callback hell" in your view?

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.

The other interesting problem is that the experts are using tools and concepts that are a long way away from a beginner and that can make it harder for beginners to learn. In Clojure when a beginner wants to accomplish a task their biggest problem is that they are solving the wrong problem or being pushed to deal with problems they don't understand.

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.

The advice the community usually gives is just use hashmaps. You can easily refactor when you need that more difficult functionality, because the language strongly disincentives mutable state, which is what usually causes issues.

If you tried Clojure a few years ago, the Tooling could be a problem. But now, the tools-deps(official Clojure cli) is awesome.

How do you find it compares to Leiningen? I'm using lein for all of my side projects (and shadow-cljs) and I like it. What does tools-deps offer me?

Tools-deps is a lightweight alternative, when working with it, the configuration mainly care about three things. The source path, the dependencies and the aliases(all kinds of entry point and launcher command). I found this solution much more clear than in Leiningen.

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.

And poor tooling support on MS Windows. Leiningen does not even have a proper installer on Windows, last time I checked.

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.

> Having leiningen not bundled into the clojure distribution, is in strong contrast to the "batteries included" approach of python.

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.

That’s an exaggeration. You’re not entirely wrong, though.

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.

Fundamentally though I thought the language ecosystem needs stability and consistency. Very many of clojure related tutorials, stack overflow answers are outdated.

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.

Most of Cognitect's projects use either Maven or tools.deps, though. I don't think I've see any of them use Leiningen, but perhaps you can show an example?

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.

Does it matter when all these tools are compatible (on the level of artifacts and distribution repository)?

For the experienced user, it doesn't matter. But for the newcomer, it's one more additional choice to make, and, as the grandparent said, is in sharp contrast to a "batteries included" approach you see in Python. Given that build tooling is one of the more frequent complaints, more fragmentation in this area is undesired.

It's funny that you mentioned Python :) https://wiki.python.org/moin/ConfigurationAndBuildTools

Are you saying that just because there is a choice of configuration build tools and utilities for Python, the argument that there is a standard way of doing things is invalid?

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.

There is such de-facto standard in Clojure too. just do `lein`. + even if you choose another build tool, it is:

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.

But as I said, lein is not distributed with clojure, and installing lein on Windows is not straightforward.

For the most part I’m not sure it matters. What specific problems have you had as a result of fragmentation in clojure project build tools? I don’t much care what the name of the command is: if running it produces an artifact I can depend on and use in my app, then it did the job. Different types of build pipelines have different needs.

For me personally it’s not a problem, but build tooling is a frequent obstacle for newcomers to the language. Fragmentation is the opposite of what we want here.

This is being addressed currently. There is now a bundled into the Clojure distribution package manager called "tools.deps".

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.

Yes, but that again begs the question why these are being addressed such late in the evolution of the language/ecosystem.

That's just a matter of man power. Things take time, this sort of tooling wasn't the priority. The community was able to easily take over that space, and relying on Maven was a great way to start off.

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.

WSL2 is great. Recently moved off MacOS to do all my dev work work on Windows.

But creative people also need to do boring stuff from time to time. If they still choose Clojure, then apparently it suffices for these tasks.

> Startup time isn't what is holding the language back.

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.

Great language and ecosystem. I donated at the beginning and made my living writing Clojure code for a few years. However, I find myself way more productive using Common Lisp, perhaps because since I have used it since 1982.

I think Clojure’s sweet spot is a small team working on a large enterprise project.

Yes, this. Tooling.

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.

You might have a biased sample of Clojurists, in my vicinity ~50% use Cursive.

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 haven't used IntelliJ. So I haven't used Cursive.

I did use Eclipse and Counterclockwise for quite a while and enjoyed it very much.

I think you are omitting the main reason why Clojure never succeeded: it's dynamically typed at the core.

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.

Static types are the new religion, like OOP was in the 90s.

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.

My experience is the opposite. Types make code much easier to refactor, in fact almost effortlessly so and with a certain confidence the code won't be hopelessly broken afterwards.

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.

I think what the most import for refactoring, is about runtime.

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.

Yeah, sometimes I look at some Typescript or Scala code and feel like I don't even understand what the heck they are trying to build anymore.

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.

You're aware that it's possible to write bad code in any language, even Clojure, right?

Look, I'm not trying to bash on static typing. I like type systems. I love Haskell's, for example. I missed static type checker in every single dynamically typed language, and Clojure is not an exception.

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 being said, there's no conclusive evidence that dynamically typed systems can't be robust and scalable.

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.

You don't have to explain benefits of statically typed languages to me, I'm not fresh from a bootcamp.

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.

Totally agree, functional programming, and certainly the kind of programming you do with clojure, can get just as messy and far away from your goals as object oriented code.

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.

> 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."

I'm a Programming Language enthusiast, currently making my own language. I'm really curious what makes Clojure different - it's pretty much the most loved language among its proponents (i.e. people love e.g. Rust and Go as well, but not as much as those who love Clojure, love Clojure). Is there any very Clojure-y code you can point to, that would showcase it? Is it just the libraries - collections, concurrency primitives - that could be replicated in another language? It's can be just homoiconicity, as that's just Lisp, but it could definitely be a big part of it... 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?

You model your logic/domain as data using immutable values, and write functions that act on that data. There are a few good things that come from this design decision.

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.

I have started programming when I was about 13-14, so it's half of my life now. Honestly, I never wanted to be a programmer. More of a writer/speaker/culture animator. I found Clojure in my first years of learning programming and, from start, it felt, like the only language that was really thought through, before creation. Clojure is the best because its syntax (or lack of thereof) along with data structures, namespaced keywords, specs, and whatnot, allows me to think properly. No other language gives me tools to think so clearly and plainly. I spent lot of time with JavaScript, some Python, some Ruby, a bit of Haskell. None of those really cares about giving you proper tools to think. When I need to use a language different than Clojure it's a burden now because I still think in Clojure. Or: I try to analyze and build a model of my domain without thinking about computers. The best programming language for that is Clojure. Other languages make you think about computers and, for me, that's waisted time

REPL driven development

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.

Since there are many answers already, I won’t elaborate too much. I just want to emphasize one thing: people mention immutable data structures as a default a lot, and sure, they’re great.

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.

I am not an expert Clojurist (yet) but I really love Clojure because it gets out of the way like Python but with the performance of Java. Almost all of the boilerplate is gone, and yes, static types next to each variable feels like boilerplate to me after doing enough Clojure.

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.

The survey answers to Q12 "How important have each of these aspects of Clojure, ClojureScript, or ClojureCLR been to you and your projects?" on the 2020 Clojure Survey results give some summary answers for several features of those languages, how important the people answering the survey finds them to be. https://www.surveymonkey.com/results/SM-CDBF7CYT7/

Being a Lisp is a big one for me for sure! Specifically, this means having a simple regular homoiconic syntax, great support for macros and meta-programming, and most important of all, a fully dynamic nature with REPL driven development and all constructs being reifiable at runtime, while still being fast and performant.

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.

Thanks! I got a few answers already, all of which were really helpful, but this one was most extensive! Another one, if you’ve time: What would you improve in Clojure?

Hum, that's an interesting question.

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.

This has been something I've been trying to answer and can't arrive at a conclusion.

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.

I don’t know what percentage of errors are caught by static typing, but I do know what percentage of my time has been saved by switching to a statically typed language. I love clojure, but I spent at least an order of magnitude more time tracking down various bugs than I do now, simply because the compiler didn’t catch them for me.

I totally respect anyone for just choosing a language that works best for them. They are tools for craftsman, and often are about which one makes you a better programmer.

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.

https://news.ycombinator.com/item?id=19131272 . It don't know where your 2% number is coming from, but in Airbnb's experience it was much more. It feel like it would also be more, imo, but I guess it depe nds.

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

Can you reference "Types solve 2% of programming errors"? I've read numerous research papers and blogs on this topic and have never seen anything as low as 2%.

Edit: Googled and nope, can't find anything.

Here are some which look at GitHub for various languages and count the defect rates by analysing commit messages:




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.

> Types solve 2% .......

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).

> like Go's

or Python's. It even has generics.

FYI: Clojure has types for performance, but not for static checking. They're called type hints: https://clojure.org/reference/java_interop#typehints

Static types turn certain runtime errors into compile time errors. When I learned Pascal in 1982 it became quite obvious what a huge benefit this was.

That said, I still like some dynamic languages for certain things.

I understand the issues with dynamic typing, but I don't think you can just compare Clojure to other dynamically typed languages, because Clojure is very data driven language. You have lists, vectors, maps and sets. Most of the time this is enough and these are the parameters to functions and also return values of those functions. And on top of that, you don't always have to think about these too, because of the unified sequence abstraction, which allows you to use the same core functions for processing/transforming those underlying types.

Python and JS succeeded long before they had a static type system. Many people still use Python and JS today without type annotations. Elixir is not statically typed either and seems to be doing fine.

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?


Describing the Clojure dev team as "trying very hard to catch up to statically typed languages by retrofitting some type system" because they work on spec is pushing a narrative that I doubt they'd agree with, both regarding spec's goals and its rhythm of development.

I'm not thinking highly of any narrative that the Clojure dev team believes. It is the pièce de résistance of the new "new" functional programming cult, which is in fact very old school.

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.

The dynamic vs static typing language tussle has been going on for pretty long. I think at this point in history the dynamic languages are doing much better than most times in PLT history. There was a time when C & Java were considered "serious" languages and dynamic languages were the underdogs.

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.

Programming is like an exam. Good students know where errors might occur after they write code. Bad students do not know whether they are right or wrong and where they are wrong. Therefore, a variety of complex and lengthy error checking systems, such as static type systems. But this is a costly effort with little effect.

Clojure would not gain widespread adoption even if all your points were fixed. The sad reality is that 99% of developers out there are instantly turned off by the parens and absence of while loops.

Clojure for a Lisp dialect has already gained quite some adoption. I like Lisp, and of course, I would love to see Lisp being used everywhere and used more. But honestly, if someone asked me ten years ago if Clojure would ever become more popular than languages like OCaml, Haskell or Scala, I would've said: "very few people would probably know that such language has ever existed." I'm not trying to bash on other PLs, the popularity of any language is difficult to measure, and it is a very subjective topic. Still, today Clojure has lots of conferences, and the number is growing every year, several active podcasts, tons of (relevant and up to date) books, jobs (that no matter how many, never will be enough), meetups, etc.

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.

I don't worry about Clojure's future either. I think it'll stay strong for at least another ten years, and it's hard to see beyond that for any language. I use it professionally, and would strongly consider it if I was founding a startup.

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.)

Ha, I did run into that one. Take my upvote! Clojure's while is very rarely used so hopefully my point still stands, especially when languages like Go have been wildly successful going in the exact opposite direction.

Clojure has while loops, but I think I see where you're coming from: It is a significantly different approach to programming, and if an engineer is not willing to re-jigger their style Clojure is not going to be a good fit for them.

I meant that they won't even try.

Those who won't even try are not worth hiring.

My personal perspective (after 8 years clojuring, half of that professionally) is that Clojure keeps progressing, with ever better tools and ideas for getting stuff done, optimally. I remain optimistic.

At the same time, it still fails at my "golden test": can I gather 5 random freelance engineers and get them to ship a project within a few months, wasting almost no billable time?

I can (and have) with Ruby, Typescript. People can learn those on the go, being productive on day 3 or so.

Clojure is still bit of a journey on itself, involving quite a lot of stuff to learn, and plenty of choices to make.

That's not necessarily a bad thing, nor Clojure's "fault", but it's a real cost/risk that is still there.

I do envision a future where the mainstream is ideologically closer to Clojure, and Clojure offers an (even) more polished set of tools than today. With that mutual proximity, Clojure could indeed eat the world.

> I can (and have) with Ruby, Typescript

That's precisely the reason why Clojure attracts seasoned, experienced, grumpy developers.

Over and over again, they have been perfecting the art of "building things fast." The problem [with the most] programming languages today, that although they provide frameworks, command-line tools, code generators, etc. to build things quickly, the codebases very quickly become difficult to maintain and scale.

So many times, I have seen it myself. I quit jobs simply because I exhausted my mental and cognitive capacity to deal with the code that I wrote myself several months ago.

While Clojure codebases, no matter how messy they can get, they feel very much like gardens - you can slowly and organically keep growing them.

Some may say: "You're talking about Haskell, or Scala, (or some other language)" And in my experience, although you can do pretty cool things in those languages, sometimes simple, dumbed-down solutions are better. Some may argue: "Now you're talking about Python, or Go...". I think Clojure has a perfect balance between "sophisticated, ultrasmart" PLs and Pls with the books titled "X for complete idiots."

I think Clojure is an ideal language for writing business apps and more and more companies starting to see that.

I don't disagree with anything you've said, and might be myself one of those grumpy programmers :)

But that isn't at odds with the view that Clojure is more fit in some environments than others, depending on existing knowledge, deadlines, budget etc.

Maybe I'd introduce Clojure in ~6 out of 10 projects. I want that N to be 9.

Maybe I’m just dense, but I certainly have not been productive with any language on day 3. (Not even Ruby, which feels comfortable faster than most languages.) I sometimes felt productive that soon but I always end up having to rewrite once I learn the language fully.

I’ve learned enough languages that I can spot “a person’s first program in X” now pretty easily.

My background is freelancing/consulting where it's not uncommon to gather diverse individuals to work on a greenfield project.

There's an implicit assumption that all participants know a thing or two about programming, and accordingly are apt learners.

So yes, on day 3 the code won't be perfect, neither on day 6 but after a few code reviews one can get stuff done.

Personally I wouldn't be comfortable prescribing Clojure for such a project, in contrast to other languages which I've never coded in, but that I know that socially, today, work better.

Yeah, same. But it does help a lot having a professionally developed project with lots of high quality, real world examples of how to do things.

Your test is basically asking “is it similar to something popular”, there will always be a learning curve when changing to a new, different from what you’re used to, tool. In my personal experience, I’ve seen people go from no Clojure or functional programming knowledge to productive in a few weeks, that’s quicker than I’ve seen some people go from non-OO to OO (although I imagine it has more to do with the people than with the paradigms)

I think it's a problem loop. No one wants to use Clojure because it's hard to impossible finding Clojure Devs. Also no one wants to learn it for the same reasons. The demand is less to non-existent to a point any time spent learning Clojure is unlikely to give any substantial returns.

I would say Clojure more than substantial returns of invested time. Not just the language itself, but also the wider approach to software composition, feature accretion vs backward compatibility, schema'd dynamic typing seem benefictial no matter what your development tools you use. Maybe check out the talks below and see if they don't enRich you as software engineer.

https://www.youtube.com/watch?v=ROor6_NGIWU The Language of the System

https://www.youtube.com/watch?v=MCZ3YgeEUPg Design, Composition, and Performance

https://www.youtube.com/watch?v=oyLBGkS5ICk Spec-ulation

https://www.youtube.com/watch?v=YR5WdGrpoug Maybe Not

If anyone reading this is in this situation: hi! I've been working in Clojure(&script) for eight years, and I'm looking for full-time work. Email is in my profile.

I shifted to writing Clojure full time in November 2017 and feel that I've improved greatly as a developer.

Other languages taught me how to solve problems their way, Clojure taught me how to solve problems.

My biggest hurdle while getting started was lack of Newbie friendly resources. And the language is kinda scary at first, specially if you are like me and have spent 5 years working with Python or JS.

Dr. Fynmann said that if you want to get better at something, teach it.

Following his advice I've published multiple articles [1][2][3][4] and given a talk about Clojure at a js conf [5].

The tooling is steadily improving. I don't think it will be as widespread as JS but I doubt if it aims to be. Clojure seems to attract a certain kind of developers anyways.


[1] What I learned after writing Clojure for 424 days, straight https://krimlabs.com/blog/clojure-424-days

Learn Clojure by building a drug dealer api [2] https://krimlabs.com/blog/clojure-drug-dealer-part-1 [3] https://krimlabs.com/blog/clojure-drug-dealer-part-2 [4] https://krimlabs.com/blog/clojure-drug-dealer-part-3

[5] If you are going to transpile JS, why not use ClojureScript? https://www.youtube.com/watch?v=Bs44qdAX5yo

Thats last few sentences make no sense. The argument is that uptake is stunted by the JVM's slow startup time, making it unsuitable for commandline utils or desktop apps. However thats easily fixed which was never more hillariously stated than when Rich Hickey did it a decade ago. A blogpost went viral and it was a thunderous critique of Clojure, focused on this single point of slow start up. In the comment section was only 1 reply from Rich:

time java -client -jar clojure.jar helloworld.clj

> system time 0.0001s

The -client param makes all the difference :)

For anyone else wondering, "holy crap, WTF does -client do?" here you go: https://stackoverflow.com/a/198651/931209

And to save folks a click:

> The Client VM compiler does not try to execute many of the more complex optimizations performed by the compiler in the Server VM, but in exchange, it requires less time to analyze and compile a piece of code. This means the Client VM can start up faster and requires a smaller memory footprint.

There's literally so many just insane things about the JVM and its variants, I totally get why some folks are like "JAVA OR DEATH." I just wish I had started learning it 20 years ago, like a lot of 'em, so it wasn't such a gigantic wall of pedantic knowledge to acquire.

> A 64-bit capable JDK currently ignores this option and instead uses the Java Hotspot Server VM.

This is an extremely important detail, as I expect pretty much everyone to run the 64bit version of the JVM.

Uhm.. I want to give the benefit of the doubt, but frankly this seems deceptive, or at least out of date, unless I'm totally missing something.

Why is there no magical --client / --script option for the Clojure ClI to make startup faster, if this is such an easy solution? Also, you show system time, but the time to load dependencies is in user time, which is slow by the nature of Clojure.


    $ time java -client -classpath /usr/local/Cellar/clojure  / clojure.main hello.clj
    Hello, world
    java -client -classpath  clojure.main scripts/script.clj    1.39s user 0.08s system 187% cpu 0.788 total

    $ time java  -classpath /usr/local/Cellar/clojure/ clojure.main hello.clj
    Hello, world
    java -classpath  clojure.main scripts/script.clj  1.37s user 0.09s system 180% cpu 0.811 total
(Excuse the details of my Clojure install location above)

User time appears slightly slower without the -client option, but probably is not statistically significant. Also, anyone who has worked on more than a toy project with Clojure knows that it's not just about the Hello World startup speed, every dependency you add has a significant impact on Clojure startup time.

As mentioned in other comments, the -client option is ignored on 64 bit JVMs, so it has no practical effect. Giving the benefit of doubt, Rich Hickey must have been using a 32 bit JVM.

That's not quite it. The client/server distinction was removed from the JVM many years ago. These days the JVM switches between modes on the fly on a per-method basis, this is called tiered compilation, so the optimisations are in effect on by default. Back in 2006 yes it may have made a big difference but it wouldn't have made startup magically instant.

IIRC Clojure apps are slow to start because they do a lot of first-time setup work that isn't actually executing user code. It's not so much a JVM problem as a Clojure-emitted-code problem. The GraalVM native-image tool fixes it because it can start an app in a pre-initialised state.

I understand, but either way, the example Rich Hickey used to execute the sample Clojure application in less than a millisecond seems impossible?

In other words, if Rich' example is real and recent, what could possibly be the environment in which this worked?

Yeah. Not sure - maybe it dates from a time when clojure was just a toy or prototype or something.

For me, scripts and command line application startup time is mostly a solved problem. You can just use ClojureScript, Babashka or Joker for scripting and CLI apps that don't need to perform too much CPU bound work. And for CPU intensive CLI apps, you can now use Graal's SubstrateVM to have a native build which starts instantly.

This is what I am referring to: http://clojure-goes-fast.com/blog/clojures-slow-start/

> As it turns out, Clojure start time is a complicated and multi-dimensional topic. Clojure projects are slow to start not only because of JVM — JVM itself starts in ~50 ms — but because of JVM specifics the classes are loaded slowly. Clojure projects are slow to start not only because of Clojure — Clojure itself starts in ~1 second — but because of Clojure specifics, the namespaces, especially not AOT-compiled one, are loaded slowly. And so on.

According to the latest status update from a member of the Clojure core team [1], we may see improvements in startup time with the next version of Clojure (1.11).

[1] https://insideclojure.org/2020/02/28/journal/

Running a simple cli application written in clojure with the help switch ensuring it doesn't do much beyond print text.

With and without -client 1 second

Built with graal 22 ms

I really like Clojure, it's a well designed language and one can get quite productive, surprisingly fast.

My core criticism that is not really mentioned in the article is the error messages. At the beginning I often felt lost and had no idea where to look if something went wrong.

I like Clojure too but its less-than-useful error messages are what got me interested in Elixir. I still get Lisp-style macros with a better syntax (for me) and outstanding error messages (generally speaking).

So this is a common issue on the clojure sub-reddit the two good responses i've seen to this is the library expound (https://github.com/bhb/expound) so you get more 'human' read-able errors and my personal favourite is Stuart Hallows blog post REPL DEBUGGING: NO STACKTRACE REQUIRED (http://blog.cognitect.com/blog/2017/6/5/repl-debugging-no-st...)

Yeah learning those first errors is a bit annoying learning what IFn is can be a bit cryptic but ClojureScript with shadow or figwheel gives really good errors

I used to work at a large Clojure shop. I've moved on, and I've heard anecdotally that they are building most new stuff in Go.

Barrier for entry and continued use in that limited experience was:

- Learning curve for new developers is very high in comparison to other languages, so moving existing engineers over sucks.

- Experienced Clojure developers want a job where they can "do Clojure", not necessarily because they're interested in the problems the business was created to solve, so hiring sucks.

- Lots of conversations turn to "the Clojure way" to solve a problem rather than using industry accepted protocols, standards and tooling. I mean, OK, but this makes interacting with the rest of the World a bit sucky.

- Most libraries never get to v1.0. Most people write 0.0.1 and may do some minor updates so you see it get to 0.0.5 and then it stays there. That's probably because it all 'just works', but it scares most devs when they see that and are about to bake it into their next release.

- Deployment is a known quantity and the JVM is useful in this regard, but it doesn't have the pure simplicity that some other runtimes do. Clojure implementations in other runtimes (Joker, Clojerl, etc.), might help or hinder here.

Startup time is a problem on CLI and desktop apps, sure. At my ex-employer, one server deployed app took several minutes to start and that had challenges.

That was not - I think - the reason the team started to look elsewhere or the reason I'm more likely to pick up Ruby, Python, Go or Elixir for my next project. There are lots of rough edges like the ones I identify above - many of them cultural - that need to be smoothed out a little, I think.

I look forward to functional methods becoming mainstream (again?), and Clojure could get there, so I wish the community luck.

Experienced Clojure developers want a job where they can "do Clojure"

This seems like a common problem with functional languages in general. I've heard the same thing about Haskell, repeatedly. Such devs search out a place where a Haskeller/Clojurist got into a tech lead position and then all pile in, before you know it the team is spending half its time writing DSL compilers instead of adding features to the product.

I look forward to functional methods becoming mainstream (again?)

Has it ever been mainstream? AFAIK it's always been this basically strange offshoot of the family tree that split off a long time in the past.

The core problems you highlight don't have any obvious solution, unfortunately. Learning materials can be improved but ultimately Lisp is very old and very unlike more modern languages. Libraries not getting to 1.0 is a symptom of a small community that derives more satisfaction from an intellectual exploration than having lots of users, combined with lack of commercially driven outcomes. Startup time I guess can be solved with technical solutions (and is being solved, as a side-effect of other projects elsewhere in the JVM ecosystem).

1. In India, there are only ~100 jobs for a Clojure developer. That includes people just throwing in Clojure just to hire Java/Scala developer.

2. The learning curve for the Clojure. Clojure is simple but it's not easy by any means.

3. JVM interop if you are not a java developer it adds in more learning time. Most of the Clojure libraries use java heavily. It's a good thing but it adds in an extra layer of learning java's ecosystem.

4. Error messages. Even after years of Clojure. I still find it hard.

5. Documentation. Most of the packages I use don't have dedicated websites or good documentation.

Even after all the issues. It's still worth the investment. I started working on a crawler for a freelance gig. we first built it using Golang. But due to its complexity and lots of bugs due to mutation. We ported it to Clojure and we are not looking back.

To be honest, I've done Clojure full-time (and exclusively) for 6 years now. I've almost never had to use Java interop. (With CLJS I did a little more JS interop).

There were always wrappers written by somebody else that always did what I was looking for.

If you're in Pune or willing to shift, Helpshift uses clojure heavily.

The most interesting Clojure-like language around is, IMO, Carp[^1]. Carp is a Lisp written in Haskell that compiles down to C with Rust ownership semantics and looks like Clojure. So you get a GC-less Lisp with fast startup time that is suitable for game development with the safety guarantees of Rust.

[^1]: https://github.com/carp-lang/Carp

It doesn't seem to have immutable data structures by default and concurrency primitives. That changes greatly how you write algorithms and structure your program.

It's cool, but it's like the complete opposite of the main ideas behind Clojure.

I thought about including this since I saw a talk about it once where the guy referenced Clojure, but they don't talk about Clojure at all in the README.md.

It'd be interesting to back the claim that "Clojure is (slowly) eating the world.".

Interestingly, that's absolutely not my perspective and I don't see any sources for that statement in the post.

I was talking about its ever expanding reach. It was more of an intertextual reference to the Andreessen Horowitz classic than an ideological statement about the popularity of Clojure, that is why I also added the (slowly).

That makes sense but it's not what comes across. The sentence immediately after says that "the language continues to grow". That's what I'm questioning.

That is my impression, especially 2019 seems to have been a turning point after 1-2 years of relative stagnation, but I get that you feel differently. That's why you switched to OCaml ;-)

I don't wanna nitpick too much (I realize that's all I'm doing). I just wanted to caution against stating what you perceive as a fact without any backing references.

Looks like its peak was in 2014[0] and since then it has been declining.


How is it declining? Every single year we're adding 3-5 new conferences, last year alone - Ukraine, Russia, Brazil, India, Canada. New books being published. There are number of active podcasts (more than for any other FP lang). Latest JVM survey shown - Clojure has become more popular than Scala (largely due to Kotlin), but still surprisingly so. There's a Clojure related post on HN top, almost every week, sometimes multiple times a week.

Sure, it ain't Typescript or whatever, but it's definitely not declining.

Maybe it just means that there is a small and vocal community around Clojure - being on HN means that something is interesting for HN community, but it does not translate to wider adoption necessarily.

When Clojure was started it didn't have much competition in JVM languages space. There was Scala only. Right now we have also Kotlin, which seems to be hitting the sweet spot between being a better Java and being difficult to grasp (like Scala or Clojure). In addition Java alone is getting better, so it is harder and harder to eat its cake.

> Maybe it just means that there is a small and vocal community around Clojure

There's relatively small but vocal Emacs community. There are no books being published; almost no podcasts; it took forever to organize a conference, but they still couldn't find a venue, so it was a video-conference. Every few years there's a new "Emacs killer" but the ecosystem (after over 40 years) is very much alive and thriving.

Same thing can be said about Clojure. It's a very vibrant ecosystem and there's a lot happening in Clojuresphere, but of course, with almost every post about Clojure, there would be at least one person to claim - "Clojure is dying." Well, if that's true, then buckle-up folks. It's going to be a very long, steady ride.

As someone who uses Clojure a lot:

It would be a lot better for language adoption if people would write more blog posts showing best practices for tooling, and less vague stuff about how awesome the language is.

For example, the startup time thing is completely irrelevant if you use Emacs/VSCode with a standalone REPL that you connect to.

It's non-trivial to set this up (more because of lack of consolidated info rather than time it takes), but it can easily be standardized across machines/envs (I Dockerize all projects in a simple way to allow team members to get started up with REPLs easily).

I totally agree, and that's why I am going to write a book about that:


It has to wait until I wrap up the books that are currently in progress [1], but I expect to start this summer.

[1] https://aiprobook.com


To put my own money when my mouth is, here is the bash script to start up a nREPL server in Docker: https://pastebin.com/PPmfDPyA

Here is the Dockerfile: https://pastebin.com/ymcUFdYT

And here is a sample deps.edn with the cider alias: https://pastebin.com/2a7vSFD7

To run this, you'd just put them all in one directory, run cider-up.sh, and then connect to the REPL with Cider in EMACS, or in Calva for VSCode with "Jack in to or Connect to REPL Server" and then "localhost" "4444"

Random fan here: you DO rock. Thanks for your incredible work on making ML a first-class citizen in Clojure!

For me, this article is spot on. I had been looking for a long time for my one language I can use for everything, and Clojure finally (just recently) got there. I can use it for scripting, writing command line apps, making backend services, writing web front-ends, making desktop applications with GUI, toying with generative art, doing data-science, making games, and more.

It really has great reach, and its only getting better. Similarly, in terms of programming language, you can use it to explore many paradigms and innovative ideas, like CSP, logic, probabilistic, functional, meta, concurrent, dynamic, typed, contracts, data-flow, OOP, condition systems, monads, etc. All this keeps me interested and constantly learning. Never having to wait for features you miss or wish you had.

I can see myself sticking with Clojure for a long time.

Last week some nice folks assured me tooling was good in Clojure; and not a total time suck, and so I'm getting ready to take the jump I think... but, one thing I'm still really skeptical about... is embodied in this paragraph:

> The way the languages are integrated today, Clojure developers doing full-stack development don’t really have to think about data serialisation/deserialisation. Writing code for frontend and backend differ mostly just in the way that the different implementations access the host platform. The functional aspects of Clojure, especially the immutability and focus on referential transparency, ensure that source code is mostly split into chunks of highly portable code, with the host-interop conveniently put aside in its own sections. You can move code between frontend and backend at your leisure.

Is it really that good using Clojure across environments and runtimes?

I ask because I tried all of this with Scala years ago (I loved the language) and Ruby to an extent, but it was pretty miserable or rather there was really no reward but more effort on my part. I.e. I'd spend more time annotating, bridging, and learning how to interact with the different runtime than I would writing actual feature code[1].

Or like what are the killer libraries everyone loves? Do they (libs/frameworks) support different clojure runtimes ("hosts?") outta the box? Is it easy to write "pure clojure" projects that work for any clojure project?

The one I know about or have heard most about was Datatomic, which always sounded freakin' brilliant, but I don't fucks with non-free databases. Arcadia I just saw and it looks cool but, it also seems like a dumb mountain to climb, if that's how I wanted to start screwing around with Clojure.

[1] Tools like Scala/ScalaJS and RubyMotion (back in the day, heh), were the reasons I ended up a polyglot. They're great tools, if you know the platform, if you don't, you're pretty much still fucked... but once you know the platform it's kind of hard to want to use them.

I can attest that full stack (server-browser) Clojure code sharing works very-very smoothly. I've been developing web applications like that for some time now, and apart from less context swithing (because it's the same language on both sides), sizeable parts of the code are cross-compiled to run both on the browser and the JVM with very little or no extra effort.

Example 1: I'm using Clojure's Spec library for validation. There is code that is used on the browser to validate the input that users enter into forms, and the same exact code is used on the HTTP endpoints that are called when the user attempts to submit the form.

Example 2: I'm using the Tongue library to provide client-side translations of the whole UI, but the same data files and library are used in some cases on the server to generate files that contain translated strings.

Example 3: The web UI logic is written using pure data manipulation (thank the Re-frame library for that) and because of that we are able to unit test it on the JVM without having to go through the complecity of launching and driving a browser on our CI server.

Thank you so much for the response!

Number 3 has me totally stoked; since that's incredibly valuable-- or like a dream to me to be honest, could you speak more about how that works? Is it just from using re-frame (which I'm looking at as I write this)? Is it able to test for visual regressions because of the pure-data UI? Or like how have you found that testing functionality in practice has it been saving y'all a lot of bugs and headaches you think?

The way Re-frame works, almost all of your application logic is represented with immutable tuples and maps (called "events" in Re-frame jargon). The parts that can't be represented this way, i.e. need to access the DOM, are moved to the edges of the codebase and are called "effects" and "coeffects". In practice, 80-90% of your application code can be represented as events, while the remainder becomes effects and coeffects. Your application logic is effectively a finite state machine and you can unit test all of it.

The visual part of Re-frame is handled by reagent, which is a minimalist React wrapper that represents React components as pure functions, JSX as built-in Clojure data structures literals (hiccup), and state transformations using Clojure's idiomatic atom data structure. Re-frame builds on top of this base, by enforcing that a single atom (called db) holds all of the state and providing a DSL to create cursors into parts of it.

Like most things in Clojure, Re-frame's initial setup is not a straitjacket and you can swap out the state atom with e.g. an in-memory datalog database using posh/re-posh and make the data retrieval completely declarative too.

Agreed... would love to hear more about this as well....

Check out my reply to rubyn00bie.

Much of the client-side code is about manipulating the DOM. Such code must assume the DOM-API exists. So such code can not run on the server unless there is some kind of ducky DOM environment there as well. And since client-side relies much on destructively manipulating the DOM, how can such code be tested on the immutable clojure server?

> sizeable parts of the code are cross-compiled to run both on the browser

But not all, right? I think the challenge would be knowing which parts can run on both the client and server, so you can know which parts can be moved between client and server.

But code is typically intended to run on either the server, or the browser. What would be the practical benefit of moving code between those environments?

I have not used Clojure so I'm asking because I have a similar circumstance with Node.js on the server, and DOM-manipulating JavaScript on the browsers, and its not easy to move code between those environments, and secondly, I don't much see the need for doing so. Naturally testing everything on the server would be nice, but I'm not sure how my DOM-manipulating client-code can be tested on the server.

I did a project where we used ClojureScript both on the server and the client. The server ran on Node.js and so most libraries could be used on both the server and client. We used reagent to render our app which made it possible to use reacts render DOM to string functionality.

Essentially the app was developed as an isometric app, and we had some additional stuff in the server part of the application.

While running ClojureScript serverside might not be everyone's cup of tea, it worked well for us.

> Much of the client-side code is about manipulating the DOM. Such code must assume the DOM-API exists. So such code can not run on the server unless there is some kind of ducky DOM environment there as well. And since client-side relies much on destructively manipulating the DOM, how can such code be tested on the immutable clojure server?

The same way as it's done in React. Think of a client app as a loop reacting to user /network events and turning state into html. The same way it's done on server.

Precisely. There's a lot of (domain) data manipulation before it gets to the DOM, and that's where cross-platform clojure (CLJC) excels.

> Think of a client app as a loop reacting to user /network events and turning state into html.

Then who, which part of the code, inserts that HTML into the DOM? Is that done by user-code or some library (when using Clojure)?

There is a library called reagent which is a wrapper for React, check it out: https://reagent-project.github.io/

It uses a the same data format as hiccup template library which can be used on server side - https://github.com/weavejester/hiccup

That's why the same code can be used on client and server. The simplicity of the process (uses pure functions to turn immutable data structures to html) means that it can be also easily unit tested.

A lot of Clojure libraries will be described as Clojure(Script) libraries which means they run on both Java and JS usually with no interface changes here are some examples: https://github.com/tonsky/datascript https://github.com/Engelberg/instaparse https://github.com/wilkerlucio/pathom

Interop with hosts is a core feature of the language so it feels natural to reach for it when you need it and it's easy to write code that handles multiple runtimes but 95% of the time maybe more you don't need explicit runtime features

My team is developing full stack apps with Clojure, and using Clojure on the backend with ClojureScript on the frontend is completely seamless.

I copresented a talk on the platform my team develops and how we leverage Clojure features there, specifically the ability to cross-compile between Clojure and ClojureScript that might be of interest https://www.youtube.com/watch?v=IekPZpfbdaI

I have been writing helper libs with our shared logic in Clojure (CLJC). It is pretty nice to have the same core on the frontend and backend. It is easy enough to use the conditionals to call a Java fn vs. Javascript for certain logic (I.e Dates, etc)

I don’t actually get the big deal about enforced immutability. Of course, you need to constrain yourself in certain circumstances to do things immutably, but it seems like something that should be contextual (immutably ...), rather than enforcing it. In fact, sometimes it is necessary in high performance multi—threaded/multi-processor environments to use side effects judiciously. Moreover, most serious lisp programmers almost write immutably by default. The only (common) exception being hash tables, but you just don’t use them if you need more control.

> but it seems like something that should be contextual

That's exactly what Clojure does. It makes immutability the default, but offer tons of mutable options with varying degree of contextual restraints for performance.

For example, by default your data-structures and record types are immutable. Now, say you can tolerate some mutability within a local context, you can make your immutable data-structures temporarily mutable (called transients), apply mutation for performance in the local context, and make it immutable again once done.

Another example is, by default, Global Variables are immutable and can't have their value changed, but, you can have them have per-thread local values, which are then allowed to be modified, since they're concurrently safe.

Another example is local variables are also immutable by default, and so are data-structures, but if you need to mutate the variable, you can make it `atomic` or `volatile` depending on the level of guarantees you need around concurrent access. Now you'll be able to change their values, for the former, using an atomic eventually consistent swap and set algorithm, and for the latter, with CPU cache synchronization.

There's more, but basically, the idea is to start with the safest default, and for every new level of: "I need a bit more performance or flexibility", there is an additional construct that can be used to enable it. All the way down to full blown mutable variables and data-structures when absolute performance is required.

> sometimes it is necessary in high performance multi—threaded/multi-processor environments to use side effects judiciously

The short answer is: Clojure (and other languages/frameworks that emphasize immutability) is not intended for those use-cases.

The long answer is, Clojure added something called "transient data structures" as a trap-door for cases where you really absolutely need mutability for performance: https://clojure.org/reference/transients But they are really intended to be the exception, not the rule.

Enforced immutability is a conscious choice you make to trade some performance for increased robustness. Also, compared to "immutability by convention", enforced immutability allows certain optimizations to be made that greatly decrease the wasted memory and allocation/deallocation that would occur if you naively cloned everything.

I haven’t really thought this through, but at the OS level we used to have the concept of reentrancy, which separated code from variables for multi-tasking. I haven’t seen this exact concept in a programming language level, although I don’t know every language, and it may exist by another name. I didn’t really think about this until now, but I naturally write reentrantly, separating the hash tables and globals For cross thread data, and writing everything else functionally, and/or using the thread system to manage all other mutables as locals (like the transient construct, I guess).

I think the idiom in Clojure is for threads to be functional, so they aren't running persistently and mutating shared state: they spin off to do nonblocking work and then return their result. While doing so they share memory so as to avoid serialization/cloning overhead, but that shared memory is an immutable data structure, so you don't really have to worry about race conditions because threads can't step on each other's toes.

It's not just about immutability. it's about the fact that you can do mutations producing a cheap copy of the data structure.

If I have an array of ten million numbers. I can alter one of those array elements, getting back a new immutable array with the modification. And fast. Yet the original array still remains without the modification. How the implementation magic works is not a concern when thinking about the abstraction. But you can read how the magic works and like any magicians trick, you end up saying "oh, so that's how it's done".

So you can modify one array element and get the performance (extremely close) to how you expect a single array element modification to perform. You the original copy of the entire array also remains if some other code has a copy of it.

Imagine a recursive algorithm searching a game board with different move possibilities. Apply a move to a board and get a new game board. Yet the original game board remains unaltered. And you didn't have to implement any magic in your search algorithm. No copying of the entire game board. You just get your new game board with the new move applied so that your recursive function can proceed.

The point is to force you to think whether mutability is necessary or not in every given case - you have to, because you have to consciously pick it.

You CAN create mutable data structures. This can be very useful LOCALLY. But then turn it into an immutable data structure as soon as you return it.

Example: a sieve function for primes. Pass it a range of n1 to n2. It seives that range, say from (1 million) to (1 million plus 100,000). It could use an efficient mutable data structure locally. Then return an immutable result. Especially since the result has no reason to EVER be mutable again. You're not going to find more or less primes within that range.

Most serious Lisp programmers write mutable code. Common Lisp is multi-paradigm but most code written has side-effects. And that’s for a good reason: Clojure-style immutable programming doesn’t fit most problems very well. Which is why Clojure will never be a language with the lasting power of Common Lisp. It prematurely optimizes and constrains your thinking into a single paradigm.

I write CL production code all the time. Basically daily. I use hash tables a lot, and those are obviously mutable (I did mention that as an exception) but most of the rest of my code is pretty much functional, which is generally (though not uniquely) immutable. I can’t really see how it makes sense to do hash table immutably. It might just be me, but functional style tends toward immutability be default.

What I want to know is where to find a Clojure and/or ClojureScript job. I got a taste of Clojure on a project late last year and became enamored with it. So much so that I would love to find a job where I can use it at least part of the time.

Try the #jobs channel of https://clojurians.slack.com/

The situation with windows is weird. You have to run a powershell script to install. They should just offer a Zip or Exe like every other programming language.


tools.deps is in alpha - even as someone that uses Clojure on Windows, I think it's fair enough that they don't care about Windows just yet.

Leiningen works great on Windows, and Boot is meant to as well (though I don't have first hand knowledge of that).

> The situation with windows is weird.

Isn't it always? Basically anything that's not related to .NET has its warts in Windows. Erlang for example.

Delphi, Python, Ruby, C++, Qt, Perl, nodejs, Java, Pharo/Smalltalk, Eiffel, Common Lisp, Ada do pretty well on Windows, without being .NET related.

Funny, I'm probably getting old, but I do remember times when every single one of these had issues in Windows. Besides, all of them are much older than Clojure.

You could try with: https://github.com/borkdude/deps.clj

It replaces the powershell code with a Clojure based executable.

While we are at it - how do you refactor a mid-sized/large Clojure project?

By writing smaller functions and avoiding positional arguments. Refactoring/restructuring in Clojure - most of the time is about breaking bigger functions into smaller ones. But if you try to write smaller functions to begin with - that significantly simplifies the task of maintaining that code.

The same way you refactor any other mid size project? Refactoring 2nd ed by Martin Fowler mainly uses Javascript and Refactoring (Ruby edition) by Jay Fields are both dynamic. Tests cover you when refactoring not types.

By making use of nice tooling like Cursive.

The Cursive keyword rename feature is nice!

TDD? Testing in Clojure has such a nice feedback loop in Cursive, clj-kondo is a nice sanity checker, and most editors have rename functionality

Long term I can see a more intelligent feedback system powered by codeq helping with static analysis and refactor feedback, like unison but better (using a real DB and open for extension)

So, my first answer is that you shouldn't have too, and if you do, you might not be writing proper Clojure code.

The most fundamental concept in Clojure, from the famous Rich Hickey talk Simple Made Easy (https://www.infoq.com/presentations/Simple-Made-Easy/) is that your code should strive to be decomplected.

That means that your program should be made of parts that when modified do not break anything else. This, in turn, means you don't really ever need to refactor anything major.

In practice, this has held true for most of my code bases.

Now, my second answer, because sometimes there are some small refactors that may still be needed, or you might deal with a Clojure code base that wasn't properly decomplected, you would do it the same way you do in any dynamic language.

The two things that are trickier to refactor in Clojure are removing/renaming keys on maps/records, and changes to a function signature. For the latter, just going through the call-sites often suffice. The former doesn't have that great solutions for now. Unit tests and specs can help catch breakage quickly. Trying out things in the REPL can as well. I tend to perform a text search of the key to find everywhere it is used, and refactor those places. That's normally what worked best for me.

It helps a lot if you write your Clojure code in a way that limits how deep you pass maps around. Prefer functions which take their input as separate parameters. Prefer using destructuring without the `:as` directive. Most importantly, design your logic within itself, and so keep your entities top level.

> Prefer functions which take their input as separate parameters.

In practice, it's better to avoid positional arguments and extensively use maps and destructuring. Of course, there's a risk of not properly passing a key in the map, but in practice that doesn't happen too often. Besides - Spec, Orchestra, tests and linters help to mitigate that risk.

> In practice, it's better to avoid positional arguments and extensively use maps and destructuring

We can agree to disagree I guess. In my experience, especially in the context of refactoring, extensive use of maps as arguments causes quite a lot of problems. Linters also do nothing for that.

Positional arguments have the benefit of being compile errors if called with wrong arity. I actually consider extensive use of maps a Clojure anti-pattern personally. Especially if you go with the style of having all your functions take a map and return a map. Now, sometimes, this is a great pattern, but one needs to be careful not to abuse it. Certain use case and scenarios benefit from this pattern, especially when the usage will be a clear data-flow of transforms over the map. If done too much though, all over the app, for everything, and especially when a function takes a map and passes it down the stack, I think it becomes an anti-pattern.

If you look at Clojure's core APIs for example, you'll see maps as arguments are only used for options. Performance is another consideration for this.

Doesn't mean you should always go positional, if you have a function taking too many arguments, or easy to mix up args, you probably want to go with named parameters instead.

For example if you have something like:

    (study [student age] ,,,)
And inside it calls a bunch of auxiliary functions where you pass either `student` or `age` depending on what those functions do, then someone says: "oh we need to also add an address", and have address verification in the midst of that pipeline. And instinctively programmer would add another positional argument. And to all auxiliary functions that require it. The problem with the positional arguments - they often lie, they're value depends on their position, both in the caller and in the callee.

It also makes it difficult to carry the data through functions in between. The only benefit that positional arguments offer is the wrong arity errors (like you noted). And yes, passing maps can cause problems, but both Joker and Kondo can catch those early, and Eastwood does that as well, although it is painfully slow. With Orchestra and properly Spec'ed functions - the missing or wrong key would fail even before you save the file. I don't even remember the last time we had a production bug due to a missing key in a map args.

But of course it all depends on what you're trying to do. I personally use positional arguments, but I try not to add more that two.

That's a bit of a different scenario then I was thinking.

In your case, you're defining a domain entity, and a function which interacts on it.

Domain entities should definitely be modeled as maps, I agree there, and probably have an accompanying spec.

That said, still, I feel the function should make it clear what subset of the entity it actually needs to operate over. That can be a doc-string, though ideally I'd prefer it is either destructuring and not using the `:as` directive, or it is exposing a function spec with an input that specifies the exact keys it's using.

Also, I wouldn't want this function to pass down the entity further. Like if study needs keys a,b but it then calls pass-exam which also needs c and d. This gets confusing fast, and hard to refactor. Because now the scope of study grows ever larger, and you can't easily tell if it needs a student with key/value c and d to be present or not.

But still, I feel since it's called "study", it feels like a side-effecting function. And I don't like those operating over domain entities. So I personally would probably use positional args or named parameters and wouldn't actually take the entity as input. So if study needs a student-id and an age, I'd just have it take that as input.

For non side-effecting fns, I'd have them take the entity and return a modified entity.

That's just my personal preference. I like to limit entity coupling. So things that don't strictly transform an entity and nothing else I generally don't have them take the entity as input, but instead specify what values they need to do whatever else they are doing. This means when I modify the entity, I have very little code to refactor, since almost nothing depends on the shape and structure of the entity.

To add to your question: does spec help here?

Short answer: "sometimes it does." More accurate short answer: "it depends."

If you want fast startup times, just live in emacs, keeping multiple shells alive underneath. You can build yourself a whole clojure repl that just lives there all the time (startup once), and a bash or whatever you like as well.

Why don’t lisp fans use older lisp implementations like Common Lisp or scheme? Is it just a lack of libraries and frameworks?

Clojure is functional to the core and innovates a lot of areas, while other Lisps are more multi-paradigm and slant towards an OOP style. If you want to work universally with persistent data structures in your code and the libraries you use, Clojure is really the only option. Clojure also modernises Lisp syntax slightly (and a bit controversially) by getting rid of lots of parentheses and introducing new styles of parens for the core built-in data structures: ( ) [ ] { } #{ }. There's a bunch of other stuff, but really, Clojure is significantly different from other lisps.

The Rationale page on clojure.org has a pretty good rundown: https://clojure.org/about/rationale

Personally, I like what Clojure has done for Lisp syntax. I don’t have the greatest eyesight anymore and Clojure makes it easier to read my code.

I agree. Greg Hendershott apparently also agreed as he ported some Clojurisms to racket in the form of a language / loadable library:


Some of it is libraries and frameworks. Some of it is the attention Clojure has paid to ergonomics. A lot of Clojure’s libraries seem to have been built by smart people for mediocre programmers (like me!). Elsewhere in lisp land it can feel like smart people wrote libraries for themselves.

For me Racket is probably the closest thing to a decent end-to-end modern lisp experience with decent libraries outside Clojure. Gerbil Scheme also looks promising.

For me I’m most often writing smallish standalone apps, which I feel are easier to make in Racket than Clojure. But between the two languages, I’d probably take Clojure if I didn’t have the JVM along for the ride. Of course the JVM is also one of Clojure’s biggest strengths. Also, I don’t trust Oracle enough to stop pretending that Graal doesn’t exist.

Aside from being faster, more mature and with better observability, why is bringing "the JVM along for the ride" different from bringing Racket's runtime along? Also, you do know that Oracle is behind OpenJDK, has been for ten years, and has recently made the JDK completely open-source and free of field-of-use restrictions for the first time in Java's history?

Off topic; But since you work at Oracle, what is the Oracle copyright terms on alternative implementations of Java? (in the light of Android lawsuits). Eg: some research or commercial implementation for a subset of java etc?

What do you mean by "alternative implementations"? OpenJDK is 100% open source, and is entirely governed by its open-source license. You can do whatever you want with -- including take just big or small pieces from it and change them -- it as long as you comply with the license. If you want to use the name "Java", your software must pass the TCK.

No. By alternative implementation I meant eg: some experimental java compiler to a different target eg: say JS / wasm, or a memory efficient class library implementation etc..

The Java spec is not open source. You could either extract as much or as little from OpenJDK and comply with its open-source license or obtain a spec license if you don't wish to open-source your implementation. In other words, you have the open-source route or the closed source route, but the closed-source route isn't free. (BTW, I am not authorized to speak on behalf of anyone other than myself, so this is just my opinion).

I haven’t figured out a way with the JVM / Clojure to compile and distribute a small self-contained bundle nearly as easily as I can with Racket.

And Graal (the option I’m aware of for compiling JVM-based code into a redistributable binary) is definitely Oracle. If I wrote more long-running code, or I could redistribute a stripped down JVM as easily as with Racket’s build tool “raco” I might have a different view, and I’d definitely prefer OpenJDK to Oracle’s.

OpenJDK is also made (primarily) by Oracle [1]; in fact, OpenJDK is the name of Oracle's one and only Java implementation project, although Oracle builds both free and support subscription binaries from it. Both OpenJDK and Graal are open source, though. OpenJDK now includes a tool called jlink [2] that allows you to create a small, self-contained bundle that includes a custom runtime as well as your app.

[1]: http://openjdk.java.net/

[2]: https://docs.oracle.com/en/java/javase/13/docs/specs/man/jli...

I’d love to see a tutorial on how to do this with Clojure—especially integrated with the Clojure build tooling.

Gerbil Scheme allowed me to be productive in Scheme. It really is a treat to work with. No tooling required other than compiling Gambit, Gerbil and having a text editor. Those three things let you ship fast binaries, with static or shared libraries. Couple that with a nice macro system (a la Racket) and you can get going really fast.

"I don’t trust Oracle enough to stop pretending that Graal doesn't exist."

Did I parse that correctly as: "I don't trust Oracle, therefore I pretend Graal doesn't exist"?

Yes :-)

> decent end-to-end modern lisp experience with decent libraries outside Clojure

Those are still LispWorks and Allegro Common Lisp.

I don’t think being a lisp is the primary reason for using Clojure, but it’s the added bonus of being a lisp designed for FP, concurrency, and host VM integration.

My personal take: CL is a multi-paradigm rummage bin that demonstrated to the world the greatness of many features (e.g. macros, CLOS), but is also beset by many design flaws--some due to genuine infelicity of conception, and some due to unavoidable limitations of software and hardware at the time. (For one thing, there are approximately 101 ways to test equality). The result is that you could be a career CL programmer and see code written in a style you've never seen before, and that it suffers from design flaws that, if fixed, would result in something that would no longer be Common Lisp. Scheme's problem is kind of the opposite: it's stripped down, but also has not purged all of CL's unfortunate baggage, and very conspicuously lacks an out-of-the-box and full-featured package manager.

Clojure, being just over 10 years old, has gotten to benefit from decades of witnessing other languages' spectacular design disasters, and it was designed by one person in a few years instead of by committee over decades. This results in a language that is much more aesthetically coherent, adhering to a pragmatic flavor of FP, and avoids a lot of gnarly warts: for instance, Clojure has one basic way to test equality irrespective of data type, and all basic data structures are immutable.

I'm not sure you've picked the right example here - far from being a gnarly wart, differences types of equality are a fundamental issue. Your language can choose to either a) hide it effectively (constraining some things) b) expose it to the user which means they have to think about it or c) try (a) but leak and give weird corner cases.

It's not obvious that (a) is superior to (b), an (c) is often what you get.

In the abstract you're right that equality is subtle and complicated, but it seems to me that in practice, most practicing software engineers just want value-equality on garden-variety data structures most of the time (remember == for Strings in Java?), and that for this use-case, a leaky abstraction is fine--preferable, even. At least, the alternative--forcing users to consider the subtleties of equality even in circumstances where it can be safely ignored and having them memorize dozens of cryptic equality operator names--is excessive and unnecessary.

I'm not really saying any of those choices is wrong (although clearly you can make design mistakes that make the leaks problematic, if you pick (c)). It's more that none of them are clearly right.

Really I'm rejecting the idea that that equality as presented in CL was any of: wrong, design by committee, or something we have learned do better since '84.

There are historical artifacts in CL that aren't great (cf filesystem stuff) but this wasn't a good example in my opinion.

I hear your argument - not fully convinced other than in a 80/20 sense. And CL was not designed as a language to leave the 20% out in the cold.

To be fair I don't find clojure compelling as a lisp but I don't think I'm being biased in above.

Yeah, equalp is a really useful abstraction. Deep equality mashed together with case-folding. Something I've always wanted.

Of course in the rare cases I want deep equality without case (and number) folding, I can always implement it myself using... ooops!

I've had a lot of success in my own language designs with dividing the problem into two cases, equality and identity, or EQUALP and EQ in CL lingo. Despite having written a ton of CL over the years, I still fail to remember the specifics of EQL & EQUAL. Defaulting to full value equality does come with a performance cost, but I feel like we're mostly past the point where we should waste precious time on these kinds of micro optimizations for most use cases.


I agree at least separating identity and equality seem to be important in practice. I'm not completely sold on where the other lines should be.

I don't love the CL implementation, particular eql vs equalp on numerics seems fiddly, and it's hard to keep all the cases separate. Homoiconicity introduces the need for a "representation equality" i guess which isn't always needed, etc.

Who knows what the lisp crowd is ?

I'd like to have a broad view of how many lispers working with one.

the few I know:

- scheme had a coma period due to specs issues (scheme small and large standard)

- commonlisp .. no idea but it seems quicklisp is enough for anybody to work, and there are many libs. Maybe not java/python levels .. but many. It's just out of the radar.

Clojure lets you leverage all libraries of the host language e.g. Java, Javascript or C#.

Many lisp fans do in fact use Common Lisp. Freenode #lisp, the term Lisp, Reddit lisp all primarily refer to and are about Common Lisp.

Additionally, there are many Lisp old timers that do not consider Clojure a Lisp.

I'm curious, what features are missing that stops Clojure from being considered a proper Lisp?

From my brief experience it can do most of the same things except reader macros.

The common talking points are

* Clojure is based on seqs rather than cons cells.

* Clojure renamed certain common functions, like car and cdr, so 30 year old Lisp example code no longer compiles.

* Clojure doesn't have implicit tail call optimisation for recursion.

* Clojure reveals its host platform when it has runtime errors.

This list doesn't strike me as the important things that Clojure is missing. They're all relatively minor matters. There are several other things that I would consider much more important.

I've gone on at some length about those things before (for example, see my long comment in this thread: https://news.ycombinator.com/item?id=22318748, ), so I won't repeat it here.

I'll just say that Clojure is pretty nice as far as it goes, and I like it when it's the right tool for the job, but I feel like it's only halfway to being a proper Lisp. Whenever I work in it for very long I always miss more complete Lisps, and I daydream about a Clojure with the missing pieces filled in.

> I daydream about a Clojure with the missing pieces filled in

Be the change you want to see in the world and start filling in the missing pieces! ;-)

That's a fair response. I've thought about working on a Clojure implementation on a Common Lisp runtime, precisely because I'd like to see a Clojure that is a real, proper Lisp.

Cloture is an interesting effort:


I hope ruricolist succeeds.

But so far I haven't actually done any work in that direction. When I work on interpreters and compilers, I generally work on trying to improve upon Common Lisp, rather than trying to help Clojure catch up to it.

Years ago, in the early 1990s, I worked on an experimental OS at Apple. It was written mostly in a Lisp called Ralph (which later evolved into Dylan). Ralph was basically Scheme's kernel operations on top of data types built on CLOS with some functional-programming idioms.

Ralph had all of the nice things I was pining for in that post that I previously linked, but it was also a smaller, simpler, and more consistent language than Common Lisp, and it was easier to learn and easier to extend.

I've been working for years now on a language that started as a Ralph embedded in Common Lisp, but which has mutated quite a bit over the years as I learned new things and experimented with adding them to my implementations. It's been complete enough for me to ship a few products with it, but it's not done, and lately I've been inclined to steer it more back toward Ralph.

Mostly. There are still a few newer features I might like to keep.

So, while I acknowledge that it's totally fair of you to exhort me to work on Clojure, and it's not necessarily a bad idea, there is another Lisp for me to work on that is dearer to me.

Clojure did not just rename a few functions. It's basically zero source code compatible with Lisps. Not only old example code does not run, nothing runs. No applications, no libraries, no tools, ... It's not even easy to port. It's a rewrite and/or redesign.

Obviously this is okay for people who don't care of the historical baggage (and want to avoid it) and who don't care about the functionality of Lisps, like interactive error handling.

Nothing is sharable between Common Lisp and Scheme, either, but Scheme is generally considered a Lisp.

That used to be different. There were Scheme programs running in both Scheme and Common Lisp or moving between them. For example the Yale Haskell compiler was originally developed in a Scheme dialect called T and then moved to Lisp (here Common Lisp) by embedding a shallow compatibility layer in Lisp. Another example is Common Music, a music composition system, which ran for a while both in Scheme and CL. Scheme itself was originally a hosted program on top of Maclisp. A few more Scheme implementations were written and/or embedded in Lisp - for example the Scheme variants for Naughty Dog's Crash Bandicoot and Jak and Daxter Playstation games were written in Common Lisp. The Scheme written by Peter Norvig was used in a content management system, embedded in CL.

Nowadays I don't think of Scheme as a mainline Lisp -> it moved from a close Lisp dialect to its own language with its own standards, books, user groups, libraries, implementations, applications, ...

Right. The reason Common Lisp is called “common” is that it has a standard that allows anyone to build a lisp from scratch that will run any common lisp code essentially completely unchanged (obviously you need to adjust a bit where you hit the OS, but even the file access methods are spec’ed). If you aren’t conformant you aren’t a Common Lisp - you could still be a lisp. Clojure is a lisp, just not a Common Lisp.

While this is not directly related to being a proper Lisp, one of the reasons (not the only one, of course) some people may prefer CL is CLOS.

It's amusing in retrospect because one of the criticisms of Common Lisp at the time was the massive size of its library.

For anyone struggling with dynamic typing in Clojure, thinking it's impossible to manage and even harder to build confidence in your code, please look at Spec and Orchestra. You can easily build predicative contracts for all of your data and know as soon as anything "goes wrong" in terms of your data shapes.

I think everyone using Clojure JVM, ClojureScript, or Clojure CLR should be doing this.

I am not sure I buy into the argument that startup time is the hurdle to Clojure adoption. If that were the case, we would have at least seen more adoption for server use cases. Applying Occam's razor, I think Clojure is not popular for the same reason that common lisp is not popular - it is simply too hard. Functions and immutability are a more difficult abstraction to grasp compared to objects and procedures and mutable state. The overhead of finding quality talent is too high, and likely always will be because of this.

Clojure is not hard. It's just different. People immediately reject anything that's different. Just like when Fibonacci tried to convince people in 13th century to use indo-arabian numerals.

I've seen people with no prior programming experience learning Clojure within a few days. And I've seen senior software developers struggling with it for months.

I think Clojure is going to be a spec for Lisp family in the future.

You probably have only scratched the surface of Lisp if you really think that.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact