I still haven't learned any flavor of Common Lisp; I've always played with Clojure and Racket to get my Lisp fix.
Still, people who I really respect have told me that Common Lisp is "better", by whatever definition of the word. I don't really see how it'd be better than Clojure but I was curious if anyone here could explain perks of Common Lisp (any version) over other variants?
One thing is that CL has a huge ecosystem and a wide variety of compilers for every purpose you might have. Quicklisp probably beats racket and scheme in this regard, but today clojure might have an edge.
When you use racket and clojure specifically, you're kind of walling yourself into one compiler and ecosystem (two, for clj/cljs). This is a significant disadvantage compared to scheme and CL.
CL the way most people write it is way too imperative and OO for me; it reads like untyped java with metaprogramming constructs. Clojure and scheme in my opinion guide you towards the more correct and principled approaches.
Out of all the lisps, regardless of which ones I like the most, I objectively write emacs lisp the most. This is has definitely influenced my opinions on CL syntax; like my weird love hate relationship with the loop macro: on one hand it's a cool, tacit construct that's impossible in a non-lisp, but on the other hand it hides a lot of complexity and is sometimes hard to get right.
- Mutual tail recursion can be achieved with the trampoline function, which comes standard in Clojure.
- It's not too hard to do OOP-like in Clojure, and there's even clever macro stuff to get logic programming and the like. I'll agree it's very much a functional-first language though.
- While there's no explicit tail recursion, the Clojure loops are basically just tail recursive. You have to be a lot more explicit about it, and I will acknowledge that's not as pretty.
The JVM startup time is a real issue, but GraalVM really does help with that; I got around 3ms of startup time last time I tried it.
I might need to play with SCBL variant still. CLOS really fascinates me.
"self tail recursion" is only a simple case of the idea of tail calls (-> tail call optimization, TCO). In a Lisp variant/implementation with TCO (generally Scheme implementations and also a bunch of CL compilers, like SBCL), ANY tail call is automatically optimized to a jump, without growing the stacks, without the need to use special construct like Clojure's loops or trampolines.
for example SBCL on ARM64
CL-USER> (defun example (a b)
(if (> a b) (sin a) (cos b)))
EXAMPLE
In above CL function, the calls to SIN and COS are tail calls. Let's look at the machine code, which is automatically and by default created by above definition:
One can see in the assembler, that the > function is called as a subroutine with BLR (-> it is not a tail call), while SIN and COS are called with a BR, basically a jump, since those are tail calls. Thus in any piece of code, the compiler recognizes the tail calls and will create the corresponding machine code jmp instruction (minus any restriction a compiler will further have).
Generally Common Lisp doesn't require TCO, though many compilers provide it as an optimization. Required is a basic low-level lexical goto construct, which is used to implement various loop.
> but GraalVM really does help with that
In something like SBCL there is no special tool/implementation (restricting dynamism) needed to create an executable. It is a function call and one second execution time away. The resulting executable starts fast and is basically a full Lisp with all features, again.
I would like all of those things, but I'm always turned off of CL for a few reasons
- Emacs is hard to learn, and just doesn't have a lot of the things that IDEs have these days. Yes, I know it's a tired argument but it's a real one. The VSCode solution works fairly well but isn't as interactive, which is kind of the whole point of using CL.
- There's a lot of weird quirks and badly named things in CommonLisp. You can definitely feel how old it is, but it's great that old libraries will still work.
- The ecosystem is hard to navigate. I have bought a few CL books and some of them really suck because they reference libraries that flat out don't exist anymore (these are books in the last 10 years, which most CL books are). You will, of course, never find an API wrapper for anything new. Those things may not be hard to work on, but it's extra work.
It's hard to move from Clojure, which has a strong (and well documented) ecosystem, a dead simple library, and good IDE support. It may not have the debugging support CL has, but between the REPL and immutable nature, it's not a huge loss. It's more debuggable than most languages.
I would also say some Clojure libraries really stand out to me from EVERY ecosystem. Electric Clojure is really cool. Tempel is a very simple encryption library but you can do so much with it. Hiccup feels so natural. It doesn't get talked about a lot, but I feel bummed using other languages because they don't have such creative libraries.
Still, people who I really respect have told me that Common Lisp is "better", by whatever definition of the word. I don't really see how it'd be better than Clojure but I was curious if anyone here could explain perks of Common Lisp (any version) over other variants?