Coming from Common Lisp, I find both versions are pretty hard to read, and I find Clojure in general difficult to grok. Every example of Clojure I see looks like the author is "code golfing". Why the instistence on such terse, cryptic identifiers? Every other Lisp, going back to the 60s, uses readable text names, and it just baffles me that Clojure decided to go a different way.
The sentence generator in my Markov text generator CL package looked like this:
> it just baffles me that Clojure decided to go a different way.
Have you scrolled to the end, or you're judging by the initial examples in the code, because author did use more sensible names in the refactored example.
But if I'd have to re-write your CL snippet in Clojure, perhaps it would look something like this (I don't know if that actually works, haven't tested it):
I don't know why would anyone find it more (or less) readable. IMO Clojure version is a bit more functional, data-transformation oriented, while CL Loop gives you explicit control flow, there's memory efficiency, it's a single-pass processing, etc. At the end of the day, it all boils down to familiarity; you dislike Clojure maybe due to lack of exposure, yet it's not a bad Lisp, it really does shine in some areas and definitely it is a simpler Lisp than CL or even Elisp - the complexity of the loop macro alone is like learning another PL, yet for someone who knows it already it may feel easier to use.
roxolotl · 3h ago
I’ve been writing a lot of functional lisp lately for the first time and one thing that’s struck me is how easy it is to refactor. Because a form can always be reduced to its result, you can pluck them out, move them around, and evaluate them at any level of the tree. While this is true of functional programming in general the structure of lisp makes its super easy in a way it’s not in a Haskell or just functional JavaScript.
perrygeo · 1h ago
S-expressions really are the game changer. Especially in a functional lisp with immutable data structures. You can lift and shift forms around with reckless abandon and never cause a bug. You're not typing characters that eventually get parsed into a tree, you're building the tree! Programming in bespoke arrangements of ascii delimiters feels like regressing to punch cards.
iLemming · 40m ago
> S-expressions really are the game changer.
I know, right? The hardest part is that no amount of explaining and convincing anyone who never tried truly grokking this aspect of the homoiconic nature of Lisps really moves the needle. It's like living in ancient Greece, trying to convince people that riding a bike is enormously fun. No matter how verbally you explain, no matter how many blueprints and drawings you produce — until you build one and teach someone to ride it, nothing would change — people will not believe you.
Same is true with Lisp; until you get to experience the structural editing and REPL-driven workflow yourself, you wouldn't even know what you're missing.
iLemming · 1h ago
> struck me is how easy it is to refactor
Yes! After a few years of using Clojure for work; Fennel for configuring anything that requires Lua; Lisp-based editor, nbb and babashka for scripting — now I just can't deal with non-lispy languages without getting mentally and emotionally exhausted. It feels so taxing to have to deal with all the syntax elements, indentation, etc. So many times my thoughts got derailed due to a missing comma in some json or slightly misindented shit in yaml.
I just don't understand how I used to happily write programs before, and I did it for over a decade. Without ever being able to "transpose these two things around", "lift this structure and move it over here", "slurp this thing inside", "raise this expression", "wrap this piece into its own block", etc. And then the actual REPL — OMG, that's a song of its own accord.
Programmers with shallow understanding and lacking experience of Lisp (or straight bias against it) are missing out so much, it really is sad.
0_gravitas · 4h ago
First few comments are nothing but shallow {rem,sn}arks on the formatting of the site, by users that don't even have the excuse of being recent joiners. If this is how we lead by example then it's no wonder why the quality of content+comments on this site are (imo) on the decline.
nightfly · 4h ago
Even as someone who lives with transparent terminals and poor color schemes the article is _very_ hard to read though. It's not a shallow remark when the formatting completely distracts/detracts from the rest of article.
gleenn · 6h ago
In general, it's a dream to test and refactor Clojure IMHO. Working with mostly static top-level functions, immutability by default, and mocking when necessary with the use of "with-redefs" makes writing tests easy. Also, the immutability makes it hard to break shared copies of memory which seemed to plague my earlier tangos with large Java code bases and standard OOP practices.
sammy0910 · 6h ago
most people I know eschew the use of with-redefs for testing because it's hard to verify that the testing environment is configured correctly as the codebase changes (but otherwise I second the points about immutability by default, and static/pure functions!)
NightMKoder · 4h ago
Agreed - concretely with-redefs forces single threaded test execution. So eg you can’t use the eftest multithreaded mode.
Explicit dynamic bindings are better if you need something like this since those are thread local.
noodletheworld · 4h ago
Seems fair, but refactoring is generally tricky and:
1) replacing a func with a simpler recursive func may or may not be ideal.
2) this fills me with no joy when I read it:
> It seems easier to start from scratch as opposed to refactor it. The characterization tests above will help ensuring that the returned hash map stays the same.
Its enormously hmm… what word to use. Ambitous? Foolish? Brave?
Anyway, taking code, writing some tests for it and then rewriting the entire thing so it passes the tests you wrote only barely falls into “refactoring”.
If anyone refactored my code base by simply rewriting from scratch the parts they dont like, I would be upset.
Rewriting things is hard, and its … brave… to think you can just write a few tests and then if they all pass its all good!
That only works in very trivial examples, and really, doesnt show case the strengths of clojure at all.
No comments yet
NightMKoder · 5h ago
Usually the controversial decision for Clojure code highlighting is rainbow parens. This color scheme is horrific and unreadable (on mobile at least).
aeonik · 5h ago
I don't like the color scheme, and in some of the snippets I don't understand the correlation, but some of them, I think the structural highlighting is very nice.
michaelteter · 2h ago
The first thing I would do would be to take some of the inner forms and turn them into functions (single responsiblity). This isn't even Clojure-specific.
Lots of little, simple functions, composed... You can still use the threading at a higher function, but instead of doing the actual work at each step in the pipeline, you call a well-named function which does what that step was doing. Yes, it may feel redundant, but it absolutely improves human comprehension because it reads like clear human instructions.
Tests become much simpler too.
The only two challenges I face with this approach is 1 - naming things, and 2 - organizing. The function names tend to become quite long, since they ideally describe what the function does.
I haven't done this lately, now that the AI tools have become pretty great; I can imagine that an AI tool would be an excellent knowledge base for these many functions, and it would probably be very successful in providing additions or modifications given the clean, clear function names and brief contents.
Barrin92 · 2h ago
>Yes, it may feel redundant, but it absolutely improves human comprehension because it reads like clear human instructions.
That should always be how code is written, including inside of functions, which is the real problem here. Something absent from the original code that is needed in a case like this isn't more functions, it's comments and good names.
Pulling something out into a function that is purely individual application logic without intent for reuse is not the right way to use a function, whose purpose is to be called, ideally somewhere else and not just once. As Ousterhout points out in his book, proper abstractions hide information and implementation, they aren't just scaffolding that increase the surface of an interface without doing anything.
leelou2 · 5h ago
This article hits on something I've been wrestling with in our codebase. The absence of static typing in Clojure makes refactoring feel like walking a tightrope without a safety net sometimes. I've found that building a solid test suite before any major refactoring is absolutely critical - it's the only reliable way to ensure you haven't broken anything when reorganizing code.
What's worked well for us is treating functions as the primary refactoring unit rather than trying to impose OO-style refactoring patterns. Breaking down large functions into smaller, well-named pure functions has provided the most bang for our buck. We've also found that leveraging spec for runtime validation gives us some of the safety of types without giving up Clojure's flexibility.
The tooling situation has improved significantly too. Cursive's refactoring tools have been solid, and I'd be curious if others have had success with the newer REPL-integrated refactoring approaches mentioned in the article. Has anyone managed to set up effective continuous integration that catches runtime errors in untested paths?
IceDane · 6h ago
It's kind of funny that this article starts by showing a completely unreadable code snippet, but not because of the code, but because of the syntax highlighting scheme. There is no version of that code, or any code for that matter, that is readable using that color scheme.
slowmovintarget · 2h ago
Funny thing for me was that it looked... OK. The syntax scheme was a little garish, sure, but I could read it. Then I realized I had Dark Reader engaged so I thought I'd turn it off.
Holy cow. That is difficult.
iLemming · 1h ago
That's the proper way of maintaining the elitist status and gatekeeping the community only "for true believers". "Looky here you muggles - yep, Lisp is unreadable as shit. Don't you even think of trying..."
Jokes aside, I feel so raddled every single time I see some Lisp-mentioning post here on HN or Reddit, where inevitably someone comes to complain how "unreadable this Lisp thing is". Not you guys, this color scheme really is some "Christmas Night in a nuthouse"; I'm just unpacking my emotional shit all over, without addressing anyone specifically.
"Lisp is unreadable"... pffft. Geez, what the fuck does that even supposed to mean? Are they programmers or fucking toddlers dealing with an alphabet soup? It's as if linguists complained about semitic languages being "unpronounceable" on some specialized forums. Seriously, can you imagine, someone logs onto a professional forum of linguists and starts berating Arabic or Hebrew, saying shit like "I've been interpreting professionally since I was nine; I am fluent in twelve different languages, but this Arabic stuff, I gotta tell you... is pure bullshit. No money in the world would make me learn or even try using it..."
How can anyone still identify as a programmer with a stance "this programming language is unreadable"? I can understand if that's been said about a PL that someone just made or some ancient thing that very few still use, but how can anyone just dismiss a 65-year-old idea that is still being actively used and for which there's no good replacement?
arijun · 6h ago
> Our mission is to take this code and make it readable
You failed. Between the unreadable text colors and the word wrap, the code is incomprehensible. I cut and pasted it into a plaintext notes app and it was way easier to understand
The sentence generator in my Markov text generator CL package looked like this:
Have you scrolled to the end, or you're judging by the initial examples in the code, because author did use more sensible names in the refactored example.
But if I'd have to re-write your CL snippet in Clojure, perhaps it would look something like this (I don't know if that actually works, haven't tested it):
I don't know why would anyone find it more (or less) readable. IMO Clojure version is a bit more functional, data-transformation oriented, while CL Loop gives you explicit control flow, there's memory efficiency, it's a single-pass processing, etc. At the end of the day, it all boils down to familiarity; you dislike Clojure maybe due to lack of exposure, yet it's not a bad Lisp, it really does shine in some areas and definitely it is a simpler Lisp than CL or even Elisp - the complexity of the loop macro alone is like learning another PL, yet for someone who knows it already it may feel easier to use.I know, right? The hardest part is that no amount of explaining and convincing anyone who never tried truly grokking this aspect of the homoiconic nature of Lisps really moves the needle. It's like living in ancient Greece, trying to convince people that riding a bike is enormously fun. No matter how verbally you explain, no matter how many blueprints and drawings you produce — until you build one and teach someone to ride it, nothing would change — people will not believe you.
Same is true with Lisp; until you get to experience the structural editing and REPL-driven workflow yourself, you wouldn't even know what you're missing.
Yes! After a few years of using Clojure for work; Fennel for configuring anything that requires Lua; Lisp-based editor, nbb and babashka for scripting — now I just can't deal with non-lispy languages without getting mentally and emotionally exhausted. It feels so taxing to have to deal with all the syntax elements, indentation, etc. So many times my thoughts got derailed due to a missing comma in some json or slightly misindented shit in yaml.
I just don't understand how I used to happily write programs before, and I did it for over a decade. Without ever being able to "transpose these two things around", "lift this structure and move it over here", "slurp this thing inside", "raise this expression", "wrap this piece into its own block", etc. And then the actual REPL — OMG, that's a song of its own accord.
Programmers with shallow understanding and lacking experience of Lisp (or straight bias against it) are missing out so much, it really is sad.
Explicit dynamic bindings are better if you need something like this since those are thread local.
1) replacing a func with a simpler recursive func may or may not be ideal.
2) this fills me with no joy when I read it:
> It seems easier to start from scratch as opposed to refactor it. The characterization tests above will help ensuring that the returned hash map stays the same.
Its enormously hmm… what word to use. Ambitous? Foolish? Brave?
Anyway, taking code, writing some tests for it and then rewriting the entire thing so it passes the tests you wrote only barely falls into “refactoring”.
If anyone refactored my code base by simply rewriting from scratch the parts they dont like, I would be upset.
Rewriting things is hard, and its … brave… to think you can just write a few tests and then if they all pass its all good!
That only works in very trivial examples, and really, doesnt show case the strengths of clojure at all.
No comments yet
Lots of little, simple functions, composed... You can still use the threading at a higher function, but instead of doing the actual work at each step in the pipeline, you call a well-named function which does what that step was doing. Yes, it may feel redundant, but it absolutely improves human comprehension because it reads like clear human instructions.
Tests become much simpler too.
The only two challenges I face with this approach is 1 - naming things, and 2 - organizing. The function names tend to become quite long, since they ideally describe what the function does.
I haven't done this lately, now that the AI tools have become pretty great; I can imagine that an AI tool would be an excellent knowledge base for these many functions, and it would probably be very successful in providing additions or modifications given the clean, clear function names and brief contents.
That should always be how code is written, including inside of functions, which is the real problem here. Something absent from the original code that is needed in a case like this isn't more functions, it's comments and good names.
Pulling something out into a function that is purely individual application logic without intent for reuse is not the right way to use a function, whose purpose is to be called, ideally somewhere else and not just once. As Ousterhout points out in his book, proper abstractions hide information and implementation, they aren't just scaffolding that increase the surface of an interface without doing anything.
Holy cow. That is difficult.
Jokes aside, I feel so raddled every single time I see some Lisp-mentioning post here on HN or Reddit, where inevitably someone comes to complain how "unreadable this Lisp thing is". Not you guys, this color scheme really is some "Christmas Night in a nuthouse"; I'm just unpacking my emotional shit all over, without addressing anyone specifically.
"Lisp is unreadable"... pffft. Geez, what the fuck does that even supposed to mean? Are they programmers or fucking toddlers dealing with an alphabet soup? It's as if linguists complained about semitic languages being "unpronounceable" on some specialized forums. Seriously, can you imagine, someone logs onto a professional forum of linguists and starts berating Arabic or Hebrew, saying shit like "I've been interpreting professionally since I was nine; I am fluent in twelve different languages, but this Arabic stuff, I gotta tell you... is pure bullshit. No money in the world would make me learn or even try using it..."
How can anyone still identify as a programmer with a stance "this programming language is unreadable"? I can understand if that's been said about a PL that someone just made or some ancient thing that very few still use, but how can anyone just dismiss a 65-year-old idea that is still being actively used and for which there's no good replacement?
You failed. Between the unreadable text colors and the word wrap, the code is incomprehensible. I cut and pasted it into a plaintext notes app and it was way easier to understand