[HN Gopher] LSP client in Clojure in 200 lines of code
       ___________________________________________________________________
        
       LSP client in Clojure in 200 lines of code
        
       Author : vlaaad
       Score  : 159 points
       Date   : 2025-05-11 17:38 UTC (1 days ago)
        
 (HTM) web link (vlaaad.github.io)
 (TXT) w3m dump (vlaaad.github.io)
        
       | whalesalad wrote:
       | This is the most Java-y Clojure I've probably ever read. Just use
       | Java? It's so verbose and complex for what it is doing. Breaking
       | this down into smaller functions and using core.async would make
       | it even more succinct.
       | 
       | Just want to emphasize this because clojure is indeed a small,
       | lesser known language that has a hard enough time attracting
       | users. This is not what anyone would consider an idiomatic
       | example of using clojure.
        
         | roenxi wrote:
         | Would it be 200 lines of Java? It'd be 200 lines of just for
         | the boilerplate. It isn't really a selling point of Clojure
         | because it is subjective, but low-syntax high-terseness look of
         | the code is in itself a reward for using the language.
         | 
         | And there isn't anything especially wrong with sticking to Java
         | primitives if someone is comfortable with them. They work fine
         | for Java programmers. The dude doesn't need to learn a new
         | async library to write an LSP client if he doesn't feel like
         | it. Code works, its easy to read, easy to understand and
         | modify.
        
           | koito17 wrote:
           | Line count is not very useful to compare without the context
           | of standard library size, third-party dependencies, etc. The
           | code in TFA depends[1] on a JSON library[2] that is about a
           | thousand lines of code (excluding tests) wrapping a Java
           | library for JSON decoding.
           | 
           | Then there's other things to consider, like the fact that
           | this LSP client, while succinct, pays not only the cost of
           | loading Jackson, but also the cost of loading clojure.core,
           | which is quite non-trivial[3]. Startup time for LSP servers
           | and clients definitely matters to some, considering that e.g.
           | even clojure-lsp recommends running native executables over
           | JAR files[4]. Can't find documentation proving it's for quick
           | startup, but it's a plausible rationale for their
           | recommendation of a binary over a JAR.
           | 
           | Note: I have used Clojure professionally and in hobby
           | projects. I think it's nice that one can interactively
           | develop a minimal LSP client and the resulting amount of work
           | is roughly 200 lines of code. I say "minimal" because it's
           | unclear how this client deals with offsets reported by LSP
           | servers, which are all given as offsets in a UTF-16 encoded
           | string. In any case, I still think advertising "LSP client in
           | 200 lines of code" hides valuable information regarding
           | functionality, implementation, "actual" code size, and trade-
           | offs made in the choice of technology stack.
           | 
           | [1] https://github.com/vlaaad/lsp-clj-
           | client/blob/a567e66/deps.e...
           | 
           | [2] https://github.com/metosin/jsonista/blob/c8f2b62/project.
           | clj...
           | 
           | [3] https://clojure-goes-fast.com/blog/clojures-slow-
           | start/#cloj...
           | 
           | [4] https://clojure-lsp.io/installation/#embedded-jar-legacy-
           | exe...
        
           | pron wrote:
           | Between records and compact classes [1] Java's boilerplate
           | isn't what it once was.
           | 
           | [1]: https://openjdk.org/jeps/512
        
         | newlisp wrote:
         | It's idiomatic "low-level" Clojure, though. Not everything is a
         | happy place where you're just manipulating maps and vectors
         | like in most examples.
        
         | 0x1ceb00da wrote:
         | > lesser known language that has a hard enough time attracting
         | users
         | 
         | For very good reasons.
        
         | dig1 wrote:
         | I don't see why this wouldn't be considered idiomatic clojure
         | code; it makes proper use of all the facilities provided by the
         | language and the main intention of this code is to follow the
         | article. Additionally, the clojure core team often encourages
         | not to shy away from using java code directly, as this approach
         | strikes a good balance between performance and language
         | expressivity.
         | 
         | > It's so verbose and complex for what it is doing. ... and
         | using core.async
         | 
         | I think this code is actually quite straightforward and easy
         | for a clojure developer to understand. In fact, using
         | core.async in this case would be overkill and could complicate
         | things further.
        
         | daveliepmann wrote:
         | This looks like the other completely normal, idiomatic Clojure
         | programs I've seen which manipulate a StringBuilder. And as
         | Clojurians go I'm far to the succinctness/concision-preferring
         | end of the spectrum.
         | 
         | I'm curious to see your core.async-based version :)
        
       | askonomm wrote:
       | Holy crap is this unreadable or what (notably the lsp-base fn).
       | There's a reason why in most Clojure companies I've worked at we
       | try to make as small functions as possible, because otherwise it
       | very very quickly becomes an unreadable mess, and you write code
       | after all for humans to read, because if you didn't, you might as
       | well just write binary. But, I'm not surprised many people don't
       | want to get into Clojure or Lisps in general, because it takes a
       | boatload of conventions and active discipline to make it a good
       | experience.
        
         | slifin wrote:
         | To me something unreadable is code that I cannot statically
         | make any assertions about the runtime behaviour of the code
         | 
         | This function you're complaining about looks like 2 virtual
         | threads doing program input reading and output writing for the
         | LSP client given some ArrayBlockingQueues in about 25-30 lines
         | 
         | If I wanted the complete story I could use Clojure's inbuilt
         | test runner to slip some ArrayBlockingQueues in there and run
         | it under record with Flowstorm
         | 
         | Then leisurely seek through the entire state of the program, to
         | get the play-by-play of how this works
         | 
         | There are so many good design choices in this language and a
         | good 30% of colleagues I run into are not even doing the basics
         | of like running a REPL, I think some people just need to clock
         | in with a decade of C# or PHP or TS or JS or Python or whatever
         | to get a taste of a language with next to no inbuilt
         | immutability, statements instead of expressions, no reload-
         | ability in the language semantics and just crapshot debuggers
         | that run in lockstep with the program execution
        
       ___________________________________________________________________
       (page generated 2025-05-12 23:02 UTC)