Table of Contents for Programming Languages: a survey
Lisp and Clojure
Because it is so well-liked, Clojure gets its own chapter.
Cons
In clojure, the 'apply' function can't take a macro as the function to apply (functions and macros can't always be composed together).
Cheat Sheet
Tutorials and books
Feature lists and discussions
Respected exemplar code
Retrospectives
Analyses
Gotchas
Opinions
- "Clojure is one of the most well-thought and well-designed languages in existence...I love Clojure as a Platonic idea, but find the JVM, Maven, and the associated ecosystem a bit heavy for my tastes." -- [2]
- "I've been a fan of Clojure since 2009. It grew rapidly for a few years, from 2009 till at least 2014. It has stalled out. This makes me sad because I've loved working with it and I think it a great community, in most ways. But it is true that the community has been unable to answer some of the criticisms leveled against it. The reliance on Emacs has meant it is not easy for beginners. On the other side, the elite (the purists?) have wanted to see Clojure be more like Scheme, or more like Racket, or more like Haskell. Clojure has offered up many interesting ideas, but perhaps hasn't quite built a complete eco-system that makes it broadly appealing." [3]
- "I really think the impedence mismatch between Clojure and the underlying Java implementation is a big, painful problem which is going mostly unacknowledged by the inner-core of the Clojure community. And that makes sense, because they're all (or mostly) "Java Guys" who are already sold on the Java ecosystem and are fine with there being a layer of spindly OO horror in their projects. They don't mind the Java layer poking through in unpleasant ways." [5]
- "Full time Clojure dev here who has been transitioning to non-Clojure projects recently, for two main reaons: 1) I've decided that static typing is the world I want to program in, for a wide range of reasons. 2) The JVM (and JS) have their limits, and Clojure is just not as portable as I'd prefer." [6]
- "slow startup and unfriendly error messages" [7]
- "awesome...amazingly simple and elegant tool, and its persistent/immutable datastructures are absolutely worth stealing as is its concept of hygenic macros via symbol qualification and probably the concept of protocols/interfaces which Clojure kinda inherits from its primary host Java." [8]
- "One of my favorite things about Clojure is the things it's said "yes" to, as first-class language builtins: char, vector, set, and map notation; first class "vars" (actually thread locals), ways to manage and dereference state, Java interop that doesn't set your hair on fire, namespaces, keywords and namespaced keywords, ...." [9]
- "...It has now been two years, and nearly all of my objections went away as the language evolved and as I adapted to using it. Two years later, I just have two notable things to grumble about...1. No condition system. This is a big deal. Clojure piggy-backs on Java's exceptions, and currently lacks a good mechanism for user-defined error handling. Unfortunately, Java exception handling also happens to suck, since it throws away data in all stack frames from the throw to the catch. Implementing better error handling is under discussion on the Clojure development page (http://dev.clojure.org/display/design/Error+Handling). (Steve complains about the lack of non-local transfer of control, and he has a point: it could be used to make an arbitrarily powerful condition system, see http://www.nhplace.com/kent/Papers/Condition-Handling-2001.html). 2. No built-in debugger. The way CL code doesn't just crash and throw away stack information is amazing. The lack of this feature in other languages is appalling." [10]
- "1. The current clojure lambda shortcut-syntax is atrocious, and we can do better. Why don't we do better? 2. Clojure could really benefit from a scheme-esque Macro-by-example library. A few exist but they seem largely ignored by the community; despite well-known-benefits to such a system in the normal daily use of macros." [11]
- "People cite many pain points when coming to Clojure. Chas Emerick summarized it well: jvm pain, docs/error msgs, deployment pain, tooling/debugging pain, domain modeling difficulty" [12]
- "Clojure has a few interesting features which are well integrated with each other, but it also comes with a lot of opinionated choices one might not like. Another thing - not a language issue per-se - is that relying on Java ecosystem can be painful (and ClojureScript?, fortunately finally bootstrapped a while back, is not quite the same language). Personally, I feel that, in pursuit of simplicity, Clojure dropped a lot of useful features. As an example, in both CL and Racket, you have 2-3 times more ways of structuring your code than in Clojure. This is important, because different parts of a codebase benefit from different structuring mechanisms - and the more choice you have, the easier it is to find the right one. In Clojure, all you have are maps and defrecord, with the latter being almost never used. Of course, you have macros to combat this, but then you're put in the shoes of language inventor and implementer, which is a lot more complexity than most programmers would like to fight just to be able to order the function definition the way they want. To summarize: in my opinion, Clojure did some things right but then got locked into that single track and refused to incorporate additional features (and lessons learned) from other Lisps. So no, I don't think it's a "nice Lisp." OTOH, it may well be a good language, especially if your problem and domain are a good fit for it."
- "It might be a nice language but not an especially nice Lisp. Lots of stuff from Lisp is missing: interpreter and compiler integrated, self hosting compiler written in Lisp, condition system with interactive error handling, nice object-system like CLOS, simpler numeric tower, good error messages, Lisp-like stack traces, image-based development with fast startup times, Tail Call Optimization, continuations, ... plus it has some strange design decisions with basic list data structures. It's 'Lisp'-like where the basic primitive list processing has been replaced with a different higher-level data structure (persistent sequences) built on top of an alien infrastructure which leaks through everywhere and which was not developed for interactive programming (the JVM)... That was one of its original design decisions: being hosted on top of the JVM." [13]
- "This brings on the table the second point: startup time. Clojure is completely unpractical for quick command line scripts." [14]
- "the error messages and the Java layer. The confusing error messages in Clojure are a common complaint. The main problem is that these errors are, mostly, Java errors. In other words, you speak Clojure and the compiler (or the runtime) speak Java. "java.lang.ClassCastException?: clojure.lang.PersistentVector? cannot be cast to java.lang.Number" This is an example. The Clojure error starts with java.lang. This error is referred to a wrong type passed to a function, but the error is in Java and talks about PersistenVector?. Other errors are even more confusing. Plus: the stack trace is the Java stack trace. And it is not easy to map that to Clojure’s calls." [15]
- "In my opinion, in Clojure, the Java layer pokes the Clojure layer way more than it should. Another example is that Clojure require to access the Java standard library in many cases. The problem with this is that Java functions and Clojure functions are not interchangable. For instance you can do "(apply clojureFunction [0 0])". But you can not do "(apply .javaFunction [0 0])". You need to wrap the Java function into a Clojure function explicitly in this way "(apply #(.javaFunction %1 %2) [0 0])". I don’t find this particularly elegant." [16]
- "Personally, I feel that, in pursuit of simplicity, Clojure dropped a lot of useful features. As an example, in both CL and Racket, you have 2-3 times more ways of structuring your code than in Clojure. This is important, because different parts of a codebase benefit from different structuring mechanisms - and the more choice you have, the easier it is to find the right one. In Clojure, all you have are maps and defrecord, with the latter being almost never used." [17]
- " It might be a nice language but not an especially nice Lisp. Lots of stuff from Lisp is missing: interpreter and compiler integrated, self hosting compiler written in Lisp, condition system with interactive error handling, nice object-system like CLOS, simpler numeric tower, good error messages, Lisp-like stack traces, image-based development with fast startup times, Tail Call Optimization, continuations, ... plus it has some strange design decisions with basic list data structures. It's 'Lisp'-like where the basic primitive list processing has been replaced with a different higher-level data structure (persistent sequences) built on top of an alien infrastructure which leaks through everywhere and which was not developed for interactive programming (the JVM)... That was one of its original design decisions: being hosted on top of the JVM." [18]
- "The best thing about Clojure is that it is immutable by default" [19]
- "The language itself was extremely basic. There was so little syntax to learn that I could be productive almost immediately." [20]
- "Clojure and F# have similar data structures but working with them in Clojure feels lower-ceremony. The literal syntax is nice and terse and all the basic data structures can be conveniently composed and transformed." [21]
- "There are many nil-friendly core functions, and some nil-adverse functions too. It’s not always clear which functions will happily propagate nils and which ones will throw exceptions, so I often find myself experimenting in the REPL to find out. It’s not something I have to worry about much, but a nil-propagation bug can be hard to debug when it happens. Personally, I think I might prefer less nil-propagation in clojure.core in some instances, but it’s very ergonomic in the large."
- "It is almost addictive to write in a Lisp. Clojure specifically. I cannot pinpoint exactly why that is, but I believe it has something to do with its neat syntax and the peace of mind it creates when writing in a mostly functional style. It feels like I don't need to worry about the language but rather about the problem." [22]
- see thread https://news.ycombinator.com/item?id=23419961
- "The fascinating thing to me about Clojure is that it's so vocally a "practical" language, and yet is a) a Lisp, and b) purely functional... I know it's technically not 100% functional, but all of its messaging highlights that philosophy as a focal point and advantage." -- https://news.ycombinator.com/item?id=23419087
- "I think the most important part about Clojure is how ridiculously straight forward it is. Once your REPL is set up and you got used to the predominantly functional API and its vocabulary, you are dealing with an extremely simple, data-driven environment that (almost) never gets in the way. Part of that is due to deviations from traditional Lisps: a more human readable API (first, rest etc.) and less homoiconicity (data structure syntax). But also things like the first class support for names, a baked in, dead simple STM..." -- https://news.ycombinator.com/item?id=23418699
- "...Historically, lisp have used something around "define" to define functions (e.g. define, defun), parenthesis as essentially the only grouping, and would use macros or special-forms rather than complex literals. Clojure is the first lisp I know of (so I may well have missed a predecessor which inspired it) which largely diverged from that." [23]
- "Clojure’s okay (and still way better than Java, IMO) but suffers from pretty poor error handling compared to other Lisp environments." [24]
- "One of the things I appreciate about Clojure is its simplicity. Before I started working with it, I was of the mindset that having more features in a language was important because that's what made the language more expressive." [25]
- "Clojure is really great this way. My fumbling about with argument order aside, I only had a couple complexity problems when I was writing it: 1. Holy hell, I always have to look up examples for the ns macro and imports. 2. Protocols, records, and reifying... there's a flowchart out there for this but it frustrated me that I didn't Just Know. Scala has a similar thing for this where sometimes Eta expansion happens and sometimes it doesn't, and you just need to know where you can use sugar. " [26]
- https://www.more-magic.net/posts/thoughts-on-clojure.html
- https://yyhh.org/blog/2021/03/how-much-can-a-clojure-developer-do-alone/
- "...I like Lisp but dislike its mutable, tail-sharing, cons-exposing lists – here, Clojure seems to have done a nice job of salvaging the right things, like macros, and not the wrong but iconic things, like naked cons)..." -- [27]
- https://blog.asciinema.org/post/smaller-faster/
- https://packetlost.dev/blog/notes-from-learning-clojure/
Library highlights
Opinionated Comparisons
- vs. Common Lisp vs. Scheme vs. Java: http://clojure.org/lisps
- vs. Common Lisp vs. Scheme: http://stackoverflow.com/questions/11223403/what-are-the-differences-between-clojure-scheme-racket-and-common-lisp
- https://www.reddit.com/r/haskell/comments/3gtbzx/what_are_haskellers_critiques_of_clojure/
- https://www.reddit.com/r/Clojure/comments/3h4qdk/what_are_clojurians_critiques_of_haskell/
- "...problem with all of these languages is that they are still obscure compared to everything else, with all that this entails: It's harder to find developers...Clojure is the JVM, again. It's also not statically typed. From everything I've seen, performance is worse than Java." -- [29]
- "Aside: I've not found Clojure to be slower than Java and I've benched it. It is fast, fast, fast. As fast as Go even doing the concurrent thing: benched that too. I've even found that Clojure can edge out Java in many cases because of its inherent laziness: the fastest way to expedite a job is not to have to do it in the first place. " -- [30]
- vs. Elixir: "Elixir, though very small, still seems to be moving forward, and perhaps has a chance to answer some of the demands that people made of Clojure (more pure? easier syntax?). " [31]
- vs. Elixir: "I really think the impedence mismatch between Clojure and the underlying Java implementation is a big, painful problem...Elixir/Erlang don't suffer from this problem, it's FP all the way down." [32]
- vs Elixir: "I think the Elixir team visibly care about ergonomics and good design, much more than the Clojure team seem to. Look at `mix` and compare it to `leiningen`, honestly. Look at Plug and Phoenix and compare to something like Luminus in Clojure-land. The difference is night-and-day. Look at how fast the `iex` repl boots up, or how fast the stop-compile-start workflow is in a Phoenix app. Even look at the content of the languages respective homepages (http://elixir-lang.org and http://clojure.org)" [33]
- vs JavaScript?, Java, Scala: "I ... dropped Clojure/ClojureScript?, even though it was my favorite language between 2012 and 2014/2015. The main reason for me is the progression of Java and JavaScript? during these years, which make ClojureScript? less useful. ES6 has completely transformed the JavaScript? development. I finally enjoy to code in JavaScript? - this wasn't the case before. On the contrary, coding in ClojureScript? became painful. It's difficult to follow the recent trends, to integrate new libraries without hassle. Currently I work on a project that use DraftJS?. I don't want to know how to integrate it in ClojureScript? ! It's a waste of time, and again the "joy of Clojure" still exists but is now less superior to the "joy of Javascript". On the Clojure/JVM side, Java 8 is far from perfect but the lambdas expressions have made the language a bit less boring. I think many developer feel less the urge to leave Java for an alternative language. And if they really want it, there is Scala, still the best strong statically typed language on the JVM, and so, for me, still the best language for backend development. In short, Clojure/ClojureScript? was for me the best language for rapid prototyping and for front development. I've never really considered it for "serious" backend applications based on a complex data models, because of its dynamic typing. However, JavaScript? development became much more pleasant, making ClojureScript? less attractive, less differentiating. I believe that today, you have to be a true Clojure fan to code in ClojureScript?." [34]
- "That's been my experience too, and people are also moving to TypeScript? in fairly large numbers as a way to make JS development more predictable." [35]
- vs. Erlang/Elixir for parallel programming: "Clojure worked for me until I started to do parallel programming. I tried to use core.async but immediately found that I'm using two distinct style of programming, and code reuse became a serious problem...this eventually lead me to drop Clojure and Clojurescript, and use Elixir and Elm instead." [36]; [37] agrees
- "For starters, it addresses everything that put me off about Common Lisp. It’s more readable. The operator set is big, but very consistent and polymorphic: all core functions work as expected with every data type. It has a strong focus on immutability and functional programming, which CL hadn’t. But it’s also a very pragmatic language: it doesn’t ask you to learn category theory or to know what a functor is in order to get why you would benefit from it. That’s the approach to functional programming that I like. I’m not saying theory isn’t important, just that I’m the kind of programmer whose interest you won’t catch with theory detached from practice. Also, the community is very active and welcoming, and the fact that it’s a JVM language makes it get a lot of attention, considering it’s a Lisp dialect. Another thing that made me feel at home about Clojure is that it has a strong philosophy and you can tell its design has been driven by it. The philosophy is not the same as the one in Python, and now I understand that that doesn’t really matter. I want consistently opinionated languages rather than bags of features you can bend to program in ten different ways; what the philosophy is is secondary to the fact that there is a philosophy." [38]
- "Q: What do you miss from Python? A: Sometimes I say, half-joking, that programming is a branch of literature. In that sense I value elegance and succinctness, I think there can be beauty in code, but I also know that beauty is completely subjective and I can’t argue about one style being better than another. I’m not talking about readability, that’s important and it’s more or less measureable; I’m talking about aesthetics. I think Python had that kind of beauty for me, which is harder to get in Clojure. Clojure can be more expressive and it’s more powerful but can easily get ugly if you don’t have a lot of discipline. Again: all subjective stuff. Truth be told, I don’t think I miss Python all that much, at least not while I’m doing Clojure. " [39]
- "Clojure’s main Achilles’ heel right now, with respect to reach, is its relatively slow application start-up time. " -- [40]
- vs Java: https://www.reddit.com/r/Clojure/comments/f791fm/what_are_java_pain_points_that_go_away_by_using/
- vs F#, for compilers: https://www.youtube.com/watch?v=t8usj1fN9rs
- https://purelyfunctional.tv/article/clojure-is-a-better-java-than-java/
- on some disadvantages of laziness: https://clojure-goes-fast.com/blog/clojures-deadly-sin/
Best Practices
- https://rasterize.io/blog/clojure-the-good-parts.html
- discussion:
- "I know I’m glad that I decided not to learn Emacs at the same time that I was learning Clojure. That could have caused me to drop both things. I think there’s this idea that you can’t learn a Lisp without Emacs, and that may have been true before, but now that there’s parinfer you can safely hack Lisp on most modern editors. Hardcore lispers may not notice how amazing of a contribution to the community this is, it makes it dramatically easier to get started with Lisp and reachable to people that wouldn’t even consider using something like Emacs. I pleasantly used Clojure with Sublime and parinfer for almost a year. Then I started reading Coders at work and saw all these amazing hackers mentioning Emacs again and again." [42]
- "Q: Is there any particular Clojure library that you would specially recommend? A: kibit I find amazing because it’s not only a useful linter (like eastwood also is), but it teaches you the standard library as you go, which can be a little bit overwhelming when you are starting out. Figwheel is another obvious one that made a big difference for me when I started to play with ClojureScript?....The Macchiato framework is doing a great job of bringing Node.js to ClojureScript?...Joker is another great one. It’s a Clojure interpreter written in Go, but what I’ve found incredibly useful is how it works as a linter, which you can easily hook to an editor. It really improved my workflow, early catching typos and bugs while I program...I’ve found lein-test-refresh and eftest to be useful" [43]
- "...what's recommended in Clojure: favor data over functions over macros." [44]
Features
Clojure.spec
An example is at the end of http://swannodette.github.io/2016/06/03/tools-for-thought .
Multimethods
Examples:
" A Clojure multimethods is a combination of a dispatch function and one or more methods, each defining its own dispatch value. The dispatching function is called first, and returns a dispatch value. This value is then matched to the correct method. Lets take a look at our previous example refactored into a multimethod.
(defmulti convert class)
(defmethod convert clojure.lang.Keyword [data]
(convert (name data)))
(defmethod convert java.lang.String [data]
(str “\”" data “\”"))
(defmethod convert nil [data]
“null”)
(defmethod convert :default [data]
(str data))
Awesome! We have our first polymorphic solution. Now we can add more data types without altering the existing functions. Let’s add a method for vectors as well.
(defmethod convert clojure.lang.PersistentVector [data]
(str “[" (join ", " (map convert data)) "]“))
Now we can also convert vectors into JSON.
In the above example, they are using “class” as the dispatch function (which returns the class of the parameter), so it works just like traditional OOP, and it even falls back on the underlying Java OOP. But we can use any function for dispatch in a multimethod.
Bozhidar Batsov offers an example using an anonymous function:
Object oriented programming in most programming languages is based on a single dispatch message passing. The object on which we invoke a method (poor choice of words, but easier to comprehend) is the receiver, the method name and it’s arguments are the message. The method’s invoked solely on the base of the type of the receiver object.
Lisps have traditionally implemented OOP with generic methods, that don’t have a receiver and are dispatched on the basis of the types of all of their arguments. In the world of multiple dispatch the more traditional single dispatch is just a special case in which only the type of the first method argument matters. Here’s a taste of multimethods in Clojure:
(defmulti my-add (fn [x y] (and (string? x) (string? y))))
(defmethod my-add true [x y]
(str x y))
(defmethod my-add false [x y]
(+ x y))
user=> (my-add 3 4) ; => 7
user=> (my-add “3″ “4″) ; => “34″
Here we defined a multi-method that behaves differently for string and numeric arguments — strings args are concatenated and numeric args are added together." -- [45]
Variants and implementations
Clojerl
Clojurescript
Jaunt
SCI
https://github.com/borkdude/sci
"Small Clojure Interpreter" (a limited dialect of Clojure)
"Currently the following special forms/macros are supported: def, fn, function literals (#(inc %)), defn, quote, do,if, if-let, if-not, if-some when, when-first, when-let, when-not, when-some, cond, let, letfn, and, or, ->, ->>, as->, comment, loop, lazy-seq, for, doseq, case, try/catch/finally, declare, cond->, cond->>, some->, require, import, in-ns, ns, binding, with-out-str, with-in-str, future. Sci also supports user defined macros."
Kalai transpiler
https://github.com/echeran/kalai
https://docs.google.com/presentation/d/1EZv44D1jTQjv6orHSGIfsa-E9OznNF4ZuFYCRtYbqW4/edit#slide=id.p
https://elangocheran.com/2020/03/18/why-clojure-lisp-is-good-for-writing-transpilers/
https://lobste.rs/s/8oxxcq/why_clojure_lisp_is_good_for_writing
uclj
https://github.com/erdos/uclj
Jank
https://jank-lang.org/