CIDER & clojure-lsp Sitting on a Tree
Less is more unless more is more.
– Anonymous
CIDER (and nREPL by association) and clojure-lsp
are often seen as competitors when it comes to Clojure development tools. You can find plenty of discussions online
discussing the merits of both tools and their approaches.1
There are two common problems that I’ve noticed:
- People tend to conflate the specific
clojure-lsp
implementation and it’s approach based on static code analysis with LSP in general. In the end they attribute to LSP some attributes that are not related to the protocol. E.g. you can totally implement a Clojure LSP server in terms of code evaluation (perhaps by using nREPL internally). - People tend to believe it’s a choice of one or the other tool, while in reality they work reasonably well in combination.
Let’s start by examining quickly CIDER’s and clojure-lsp
’s strengths and limitations. That will make it easier to understand why they are partners rather than competitors.
The Case for clojure-lsp
Disclaimer: I never really used clojure-lsp
, so I might be missing something.
- It doesn’t require you to have a running a REPL
- It operates on your entire codebase, not just the parts that are currently loaded (which makes it shine in areas like “find references” and refactorings across multiple source files)
- If you’re already using
lsp-mode
oreglot
you don’t have to learn anything new to work with Clojure
Out of those benefits - only the last one is something we can attribute to the use of LSP, and I guess that the static analysis part of the clojure-lsp
codebase is quite usable even without LSP.
The Case against clojure-lsp
- You’ll (likely) still need a REPL for optimal results with Clojure
- The LSP protocol is generic, which is great for building clients implementing it, but imposes some limitations with the functionality it can provide out-of-the-box
Basically, it seems to me that to leverage the full power of Clojure you’ll still need some REPL-powered tool, alongside clojure-lsp
.
The Case for CIDER
- Powered by a the classic REPL-driven/runtime introspection Lisp approach
- The information it obtains from the Clojure runtime is guaranteed to be accurate (although it might also be stale in the sense that the latest source code might not reflect what’s currently loaded in the runtime)
- All of CIDER’s functionality is optimized for Clojure, as it’s not restrained by the LSP protocol
In a nutshell, CIDER is an embodiment of the concept of interactive programming (a.k.a. REPL-driven programming). I grew fascinated by it when I started to use Emacs and subsequently SLIME for Common Lisp programming, and those tools are the direct inspiration for CIDER.
Now that I’m quite used to this style of programming for me it feels like the most natural thing in the world. I’ll admit, though, that coming to it from Java and IntelliJ IDEA definitely wasn’t easy and I was originally quite confused by many of the concepts that were new to me.
The Case against CIDER
- It’s (currently) coupled with nREPL, which might be a problem for some people. (e.g. those who prefer a different REPL server)
- Powered by a the classic REPL-driven Lisp approach (yeah, that’s both a feature and a problem depending on your perspective) - by this I mean mostly that you can run into issues like loaded code being out-of-sync with your sources files and that operations like “find usages across a project” can’t be implemented reliably.
- Working on multiple projects introduces extra complexity, as you need some session/connection management.
- Steep learning curve.
Making them work together
In general I think that clojure-lsp
is the clear winner until you decide to
start a Clojure REPL.2 If you’re the type of person who likes to work without a
REPL you’ll definitely appreciate clojure-lsp
. Perhaps you can even get away
without using something like CIDER at all, but at least for me - the big feature
of Lisp is interactive programming, so I can’t imagine anyone programming
without a REPL.
Once a REPL is up and running things become a lot more dicey. Why so? Because both tools provide some similar functionality (e.g. completions, eldoc, find references, etc) and you’ll have to spend some time on the configuration to decide what to use.
CIDER could have suppressed all the lsp-mode
/eglot
provided features when
active and they could have done the same, but that’s not the spirit of Emacs,
right?3
That’s why I believe it should be up to the end users to decide which features
they want to leverage from both tools. lsp-mode
already provides a lot of
documentation on the
topic, and I
guess it’d be nice if CIDER had more coverage as well. That’s definitely
something I plan to work on, but contributions are always welcome.
REPL-driven vs Static Code Analysis
While the article mostly focuses on CIDER and clojure-lsp
it could have easily
been about REPL-driven and static analysis powered Clojure tooling instead. CIDER has
long had an extension called clj-refactor
that makes heavy use of static
analysis. clj-kondo
is another great tool powered by static analysis, that
combines well with CIDER. (and so it happens that clojure-lsp
is using internally
clj-kondo
’s code analyzer)
While people often speak about such tools as competitors, in practice they are often complementary and you’d better results if you combine some of them together. In the context of Clojure it seems prudent to mix and match features powered by runtime analysis and static analysis - e.g. I’d prefer my completions to come from the runtime, but I prefer “find references”, linting and code refactorings to be implemented in terms of static analysis.
Epilogue
I think that in the end of the day what’s the right tool for someone really
depends on their preferences and their style of programming. I’ve been
programming in Lisps for almost 20 years and I grew super accustomed to the
REPL-driven style of programming, so I don’t mind its limitations much.4 That
being said I can definitely see the benefit of using something like
clojure-lsp
alongside CIDER, and I’ll keep
working towards making CIDER play better with clojure-lsp
.
Keep hacking!