There was a recent brouhaha in the Clojure community about
the recent blog post
by a Clojure dabbler to the effect that Clojure may be a clean and beautiful language
but that it fails in a few pragmatic and ergonomic senses which hurts its adoption
and limits its appeal. Although the author admits that he probably made a mistake
in jumping to adopt Clojure, a foreign technological concept to him,
for a startup in a space that was also completely foreign to him, he does bring
up some worthwhile points that are worth chewing over.
Clojure’s Paltry Introduction for Beginners
It is a well-known issue in the Clojure community that getting started with
Clojure is extremely difficult for both beginners to programming, as well
as to those without hardcore programming experience. Here are just a few
of the challenges beginners face:
Development Environment Setup is Non-trivial
Getting a development environment set up is non-trivial, to say the least.
Although the Leiningen project is excellent
at helping individuals install and bootstrap a working Clojure environment,
getting libraries installed and launching a REPL, Leiningen (on its own)
does not help would-be developers learn the Clojure development experience.
Although there are a few environments that somewhat simplify the experience,
they are all imperfect in various ways:
Cursive is a plugin for IntelliJ IDEA which
provides third-party support for Clojure within an existing IntelliJ
installation (even the free version). This is one of the most fully-featured
IDE’s for Clojure. However, it suffers from the fact that it’s a plugin
within an existing IDE (which means finding and configuring it is more difficult),
and that you must learn quite a bit about configuring IntelliJ itself in
addition to Cursive. As IntelliJ is a heavy-weight Java IDE, learning Cursive
is not for the faint of heart for programming/JVM beginners.
Cider is a plugin/mode ecosystem
for programming in Clojure in Emacs. This is a well-maintained, highly polished,
very deep mode for Emacs and it works well. However, like Cursive (in its own way),
it requires very deep buy-in for a beginner. Emacs can take an extensive period of
time to learn, and because Cider fancies itself as an Emacs-native environment
within Emacs, it is probably impossible to use Cider without first/concurrently
learning Emacs. (I understand there is a similar project for Vim called
“Fireplace”
but I believe it uses the same backend as Cider and requires
equal buy-in to Vim, which is even less beginner-friendly than Emacs.)
Light Table IDE was actually developed in a dialect
of Clojure (ClojureScript) and comes fairly close
to being a fluent Clojure IDE. However, Light Table is not actively developed
or maintained, and is not a production-level IDE for anything, let alone for
Clojure. It’s good for introductions but is not really something a Clojure
developer should invest heavily in.
There is a project similar to Light Table called Night Code,
which is a cute Swing-based Clojure IDE written in Java which attempts to be
a Clojure-specific starter IDE (like Light Table) but with more templates.
Night Code is nice in that it has templates for creating new projects, and it
is aware of your code so that you can connect to it. However, like Light Table,
it is not a true IDE, and is not ideal for serious Clojure coding.
In short, there is nothing for Clojure like there is for Java/C#/Python/Ruby - no
dedicated, standalone IDE which can be installed and out-of-the-box support
Clojure with simple documentation and a straightforward learning curve from
beginner usage to being able to develop production apps. This limits Clojure’s
appeal because beginners either must be veteran e.g. Java programmers moving onto
Clojure with their existing JVM and IntelliJ knowledge; or they can experiment
using the lightweight editors, but will not learn about techniques like debugging
and refactoring, which will make advancing to higher levels of programming more
difficult.
Opaque or Bizarre Error Messages
I won’t spill much (digital) ink on this point because it’s both rather well
known, and also an area of active research (although not finished yet). Clojure
is a Lisp (an interpreted, dynamic language) canonically hosted on the Java virtual machine
and written in Java (which is yet another interpreted, dynamic language, albeit
one with a compilation phase which reduces the dynamics of the language at runtime.)
When an error happens in Clojure, for performance reasons, the error is caught
by the runtime of the JVM, which knows nothing about either Clojure or why this
embedded language allowed this error to occur. This results in an awkward
dichotomy for both veteran JVM programmers new to Clojure, as well as general
beginners:
If the error is due to a misuse of JVM constructs or a mistake in writing
Clojure code, the error will probably extensively mention JVM types, but will
actually be an error in the usage of the Clojure language. These errors can
be recognized with experience but are not always clearly called-out.
If the error is due to a mistake in the usage of a Clojure API, the Clojure
runtime will sometimes call this out, but often, a Java stack trace is also
printed, which confuses the issue of where the error lies. (In this case it
is almost universally in the usage of the API’s within syntactically valid
Clojure, but to a beginner that nuance is blurry at best.)
There are no perfect solutions here, due to the dynamic and interpreted nature
of Clojure. One catch-all approach with horrifying performance implications for
production code is to add several layers of parsing, validation, and error reporting
to all Clojure code, to catch and clearly elucidate the first variant of error,
as well as to present less ambiguous messages for the latter error type. Because
Clojure envisions itself as a production-friendly language, adopting these expensive
runtime checks is out of the question.
The approach the authors of Clojure seem to be embracing is to introduce a static
type-checking runtime to be bolted-onto the language, called
core.spec
(inspired by Typed Racket).
The idea is essentially to enable developers to toggle a flag in the runtime to
indicate whether to perform extensive validations and type-checking at the language
level, and if so, to validate API’s.
- For beginners, this will provide friendlier errors without any real loss, since
beginners are unlikely to be notice performance losses due to validations.
- For developers using a development-to-production pipeline, this allows developers
to validate their logic without hurting performance in production.
Although core.spec
is a clever way to attempt to have one’s cake and eat it
too, it isn’t the panacea that some hope it would have been.
- It is fairly late to the game (ten years after the language first debuted)
- It’s unclear when it will land in the core runtime, which is fairly important
for beginners who shouldn’t be expected to independently install such an
important module
- It still can’t catch issues where Java/JVM API’s are misused or invalid Clojure
code is constructed (both of which would probably still emit cryptic error
messages to beginners).
Clojure Values Conceptual Purity Over Developer Happiness
This point in the author’s essay relates to a popular but also mildly controversial
talk given by Clojure’s author, Rich Hickey, titled
“Simple Made Easy”.
Rich argued for designing clean API’s which do not introduce unnecessary complexity
at a runtime/language/library level, even if doing so required making
development less “pleasant” or “easy” for developers
(for some definition of those words). This can be differentiated almost directly
to a language like Ruby, which permits all sorts of hacky modifications to the
runtime in the interest of developer ease, despite potentially complicating
the runtime and API surface.
This core value is an essential part of Clojure’s mission statement. It’s
impossible to imagine Clojure, with its clean API’s and nearly-fanatical avoidance
of impurity, without this concept front and center.
However, this concept and its ramifications arguably also reflect on the difficulty
of setting up a nice Clojure code base. Although ActiveSupport does terrible things
to the Ruby runtime, the ability for a beginner to type code like 2.days_ago
and get a meaningful result is breathtakingly joyous. Compare that to having to
call a Clojure wrapper over a Java time API like
java.time.ZonedDateTime.now().minusDays(2)
using something like
(.minusDays (java.time.ZonedDateTime/now) 2)
, which requires learning that
Java has multiple datetime libraries, that Java 8’s libraries are better than
both java.util.Date
and Joda time, etc.
Clojure and its community generally prefer writing libraries and glueing them
together by hand, rather than having a framework like Rails that has some sensible
opinions that bypass the bikeshedding and let developers ship business logic.
Although it’s nice that Clojure apps can be pure because libraries are invoked
manually, it’s not nice to beginners that they must teach themselves all of web
programming and how to safely and properly wire up calls, when Rails has done
this out of the box for over a decade.
Clojure’s Primary “Killer App” is Proprietary
There is an argument out there that languages become popular because they have
one or more “killer apps” written in them which motivate developers to
flock to that ecosystem. Ruby obviously had Ruby on Rails; in recent years,
Python has had many numerical analysis and machine learning libraries such as
Numpy, Pandas, Scikit-Learn, and TensorFlow. Although Java doesn’t have any
one killer app, it’s both an established enterprise player, as well as host
to most of the Apache applications (each of which is itself a killer app).
(Although Apache Storm is an honorable mention as a
production quality Clojure application, it does not technically count for two
reasons. First, the project was before-its-time and appears less popular than both
Apache Spark’s Streaming and Apache Flink, neither of which are true streaming
solutions and both of which post-date Storm. Second, although Storm is primarily
written in Clojure, its API’s are heavily buried in Java API’s to the extent that
most users are probably unaware of its Clojure implementations.)
Rich Hickey and Cognitect developed a killer app in and for Clojure — the
distributed database system Datomic. Datomic is an
attempt at creating a Datalog-powered fact-database, albeit one with clever
locking, distribution, and caching mechanisms baked in. Datomic is truly elegant
in that it handles distributed reads/writes/locking, maintains an immutable
history (enabling audits and time traveling), caches in client memory (e.g. no
separate cache layer required), and that it bakes in its own ORM (if you’re okay
with all your objects looking like Clojure maps).
However, Datomic is an exclusively commercial offering. This does partially make
sense; it relies upon backends like AWS, and its appeal as a database is to those
who would want its powerful enterprise capabilities and its all-in-one nature.
However, it’s a loss for the community that such a powerful and motivating app
sits behind a paywall. If people had to pay for Ruby on Rails, it’s arguable that
both Ruby and Rails would not be where they are today; but the fact that they were
both free meant that a community rapidly formed around Rails, even despite the fact
that it was written in Ruby. If both Clojure and Datomic were completely free,
more people might flock to Clojure to be able to more easily consume Datomic.
Clojure’s Maintainers Do Not Communicate Enough
There was another Clojure controversy which managed to span nearly a year:
introducing tuples to Clojure.
Zach Tellman, a prominent Clojure library author,
attempted to contribute a patch to Clojure to provide some data structures which
he believed would be more performant and optimal for Clojure’s runtime. After
nearly nine months of discussion and waiting for consideration, Rich Hickey
(Clojure’s author) turned around and made his own tweak without informing Zach
and without bringing him into the discussion until after the change had already
been shipped. Although he made strong technical arguments for doing things his
way, his behavior was not the kind you would expect from a gracious open source
contributor.
Zach eventually wrote an essay expressing his sentiments about the Clojure development model.
It may not be fair to characterize what transpired as backroom-cigar-parlor
planning on the part of Hickey/Cognitect, but it definitely flies in the face of
the spirit of modern open source software to have an openly visible code base
but a closed development model. If open source contributors can’t be treated as
proper contributors and have proper discussions with the language maintainers
as code develops, then the code base is clearly not open, despite its source
being made available. It would be more honest in this case for Clojure’s
maintainers to officially announce that bug fixes may be accepted but that feature
requests and code reviews may and probably will be rejected at an unspecified
future date.
Where to Go from Here?
I still think Clojure is one of the most elegant languages I’ve ever worked with,
and I think that it’s still a true beauty to work with once you’ve gotten going
with it. However, between a steep learning curve, lack of open-source killer apps,
lack of bidirectional openness with the community, and a lack of fun Clojure
libraries which speak to developer happiness, I do tend to agree with the assessment
that Clojure is stagnating (I ultiamtely disagree with the author of the article and
wouldn’t go so far as to say it’s dying). With modern and fun languages which
give many of the same benefits of Clojure but with easier learning curves and
cleaner and easier experiences such as Kotlin, Elixir, Golang, etc., Clojure
couldn’t really afford to play things out the way it did. I’ll continue to keep
an eye on Clojure and see where using it makes sense; but I suspect it’s already
lost the war of developer interest (and my own, for now).
References