Paredit / Parinfer ruined other languages for me. It lets you navigate up/down/in/out of the Clojure AST with keyboard commands and mutate those expressions, e.g. "Split" will split open the current data structure you're in: `(a| b)` =Split=> `(a)| (b)`, where | is caret. Join is the inverse, and it works for all data structures.
HiPhish 12 hours ago [-]
> I am now generating this website with Clojure
As everyone knows, you are not a true lisper until you have written your own static site generator.
It gave me such a great high with how easy it was to add my own "templating engine" on top, implemented all using macros. The downside is that the crash came hard; there is so much more to a good static site generator such as optimizing the output, supporting scoped CSS, server-side rendering of SPA framework components, and of course integration with the Node ecosystem (for better or for worse there is just so much useful stuff). I have since moved over to Astro. It's still fascinating how far I was able to push my own SSG all by myself though.
embedding-shape 12 hours ago [-]
Heh, inspired by hiccup, I ended up implementing my favorite Clojure templating library but in Nix, exactly for the purpose of static site generation :) Even have a nifty demo of how it looks for that, it basically looks/works the same as hiccup: https://emsh.cat/niccup/examples/blog/
tymscar 11 hours ago [-]
This is awesome, thanks for sharing
fp64 3 hours ago [-]
Funny, learning Janet I exactly did that. Was quite a fun experience with the built-in PEG, so I did markdown parsing from scratch. Maybe eventually I will be a true lisper (fell in love with Scheme over 20 years ago but could never really use any lisp professionally. Now I at least do some small things in Clojure and babashka. I love babashka)
acdw 7 hours ago [-]
Oh I've written an SSG in multiple variations of lisp, as well as sh, make, and most other languages I toy around with. It's been a good "kick the tires" project but I think I need a new one.
shevy-java 1 hours ago [-]
> As everyone knows, you are not a true lisper until you have written your own static site generator.
I think that part is quite normal. I use ruby for the same purpose,
though the only difference is that the code I use is also to be
used for dynamic websites at the same time (cgi, rack, sinatra, in
theory ruby on rails but I just can't stand rails and DHH these
days, so I am in the opposition crowd). Using static websites, though,
always feel as if I have significantly less flexibility. I do generate
some static .html files as well, but they feel less useful to me, aside
from being displayed faster, of course.
tmpz22 8 hours ago [-]
Yeah integrating NPM is the big one, then you’re whole day converts to recovering from breaches
Jeaye 10 hours ago [-]
Once you learn Clojure's syntax and semantics, you're no longer bound to the JVM. There's ClojureScript (JS), ClojureCLR, ClojureDart, jank (C++), Basilisp (Python), babashka (SCI), and many others. This means that, if you don't know Java or don't like the JVM, you can likely use Clojure wherever you already feel most comfortable.
For the most part, any Clojure code which doesn't use host interop will work on all dialects. Clojure also has support for conditional code, depending on the current dialect.
This is one of Clojure's superpowers.
Hammershaft 5 hours ago [-]
As someone who loves Clojure, I wonder about the real portability across host languages. Do you have experience with any of these other dialects? (beyond the obvious CLJS & Babashka?)
for JS there is also Squint which is a light-weight ClojureScript dialect without the Google Closure Compiler
pdimitar 12 hours ago [-]
With respect, this topic in particular has been beaten to death.
I too liked Clojure when I tried it some years ago (agreed on the composition and data structures; both are _great_). But the real value-add is in the runtime, not the syntax. Java has a solid runtime but it's not yet as good as Erlang's, maybe even not up to the standards of Golang -- I am talking concurrency / parallelism here (for memory management I have no doubts Java is very good). And I know: green threads and stuff. Well, call me when you can do what Erlang / Golang can do. Then I'll look again, very seriously too.
Programming language syntax scarcely matters. It does to some extent but we the programmers tend to over-romanticize it. The runtime and its properties are the much better thing to optimize for.
lgrapenthin 8 hours ago [-]
Clojure is about its rigorous and pragmatic "immutability first" paradigm that you simply don't get from other PLs.
LISP is much more than just a runtime syntax, such as its distinct evaluation model and metalinguistic core.
The JVM was chosen for Clojure because of its reach and vast ecosystem. People have ported Clojure to other runtimes, even Beam (Clojerl), where it enjoys decent success, too.
pdimitar 8 hours ago [-]
All true. And I loved trying Clojure for 3-4 weeks some years ago.
Still want Erlang's runtime though i.e. the many green threads with share-nothing architecture that can communicate with each other.
sorry_i_lisp 5 hours ago [-]
Clojure on JVM with virtual-threads (green threads) and communicating only via core.async channels (CSP inspired) using immutable data structures is pretty neat, FWIW.
thaumasiotes 8 hours ago [-]
You don't think Erlang has an "immutability first" paradigm?
midnight_eclair 2 hours ago [-]
> the real value-add is in the runtime, not the syntax. Java has a solid runtime but it's not yet as good as Erlang's, maybe even not up to the standards of Golang
won't lie, this is hilarious. you got me from nodding along to being the spitting out food meme guy in a span of couple seconds.
JVM runtime is undeniably the most well researched and optimized runtime in history of runtimes, specifically in realm of concurrency and parallelism, it literally carries like half the world on it's back.
not to throw any shade on erlang vm - i've been a fan for well more than a decade, but other than making some interesting, but limited in practice, tradeoffs with regard to concurrency architecture, it doesn't really offer much more.
go's runtime is just a different beast altogether designed with different goals in mind and with no baggage of backward compatibility with legacy.
one particular detail i'm very grateful to Clojure for, is exactly the ability to use JVM runtime without having to touch any Java.
> Programming language syntax scarcely matters
on the contrary, it matters quite a lot.
you might be drinking some of that AI koolaid, conflating our suddenly hypertrophied abilities to produce code regardless of our familiarity with the syntax or the APIs with ability to produce and deliver good quality products, but this delusion is getting reality check as we speak.
a realization is propagating through the industry that being able to produce more code than you're able to review, comprehend and internalize is actually not a great thing.
and that's where syntax matters - it has to be high signal/noise, it has to expose you to right abstractions and it has to be pliable to allow the codebase reflect the problem in a way that minimizes cognitive load both during production and during consumption.
LLMs are language models and syntax is a crucial part of any language.
DarkNova6 34 minutes ago [-]
I think you might reevaluate the runtime claim, since the JVM is perfectly capable of stackful coroutines these says.
scruple 10 hours ago [-]
Have you tried Lisp Flavored Erlang [0]? I never got around to trying it out. I used Elixir for a couple of years, building web backends, and I truly loved the experience. I remember wanting to try out LFE but never got around to it before moving on to a different employer/stack.
I have and I did kind of like it but ultimately admitted to myself that I no longer want to use too niche or too new PLs. Elixir has a fairly solid ecosystem at this point and I am only going to switch to something even bigger (I already use Goland and Rust as well).
Love the idea of LFE but it needs a bigger ecosystem.
manoDev 9 hours ago [-]
> Programming language syntax scarcely matters.
Clojure brings more than syntax though... there's an opinionated take on making all data structures immutable (as in, structural sharing [1]) by default. That's a huge difference in how you architect the program and debug it.
When it comes to concurrency, what can golang's runtime do that is so special? When I tried it, it seemed like a worse version of Erlang's for people that prefer C style syntax. Depending upon your design space pervasive immutability is a huge boon too and golang doesn't have that but Clojure does - Erlang obviously having that and more.
I always wished clojerl took off.
pdimitar 12 hours ago [-]
I agree Golang is a worse version of OTP, no question about it, but if you are not allowed to code in Erlang/Elixir/Gleam (which sadly is 99.9% of the projects on the planet) then Golang is the next best thing.
It has footguns, sure, but with library support and discipline it can get you very far.
To me it's embarrassing that PLs still tout syntax and various other goodies, completely glossing over runtime. I might be missing something. But faux humble statements aside, I feel many others are the ones who miss something -- and that's the fact that doing stuff in parallel is a fact of life for 20+ years now and it's time all popular PL runtimes finally wake up to that fact.
If not, I am simply not considering them. And I am not saying that arrogantly though it sounds that way; there are some PLs that I _really_ liked and was almost heart-broken that I had to abandon them and not work professionally with them. But I have enough experience to know that runtime choice matters, a lot.
For the record, Racket was one of those PLs I abandoned. I know they started working on parallelism some years ago but I had to make a decision next week back then so, Elixir + Golang + Rust it is for me.
weavejester 11 hours ago [-]
> Programming language syntax scarcely matters. It does to some extent but we the programmers tend to over-romanticize it. The runtime and its properties are the much better thing to optimize for.
I'm not sure I understand this argument. Java and Clojure share a runtime, but an idiomatic Java codebase is going to have a very different architecture and design to an idiomatic Clojure codebase. Conversely, a codebase written in Go may end up looking very similar to a codebase written in Java, despite using different runtimes.
pdimitar 11 hours ago [-]
I mean runtime guarantees and features. In this case: effortless / near-invisible concurrency and parallelism.
As mentioned, I did like Clojure. I'd switch to it if it was running inside the Erlang runtime (like Elixir does).
weavejester 10 hours ago [-]
To be clear, I'm not questioning your choice of runtime or language. I'm just curious why you think that "Programming language syntax scarcely matters", as to me that seems the same as saying "How a codebase is architectured and designed scarcely matters".
pdimitar 10 hours ago [-]
I don't see how the latter follows from the former? The former is much bigger and more abstract; syntax is just one of the vehicles to try and codify it.
F.ex. if you have an universal construct of green threads / fibers then 7 PLs could express it 7 different ways, yet underneath they'd all be the same.
weavejester 10 hours ago [-]
The programming language informs the design of the system. As I said in my earlier comment, an idiomatic Java codebase is going to be designed very differently to an idiomatic Clojure codebase, even if they both intend to solve the same problem.
pdimitar 9 hours ago [-]
But that's still not a function of the syntax per se; Java has no immutability encoded in its runtime, hence it does not offer it as a syntax either.
hibikir 9 hours ago [-]
Scala has no immutability encoded in its runtime either (as it's the same as Java), but yet syntactically it's immutable in practice. Will the JRE technically allow a val to be edited through some third party thread inspecting your code and messing with memory? Sure. But it's not a reasonable fear in any real world environment, where I cannot remember, in 15+ years of professional scala, a case where anything I expected to be immutable (everything) to be mutated under me. Nowadays people using in in an FP style don't even think of the physical threads, as green thread libraries are taking care of all the scheduling.
So focusing on the runtime's guarantees doesn't seem like a practicality focused argument to me.
pdimitar 9 hours ago [-]
You are citing a commendable exception (Scala) to tear down a bigger argument which is not exactly a fair discussion.
Furthermore, if you trace my comments, you'll see that I had to choose PLs years ago (12+ to be precise). Things were quite different at the time. Java might have almost caught up today; back then we couldn't even be certain `synchronized` is stable all the time. Just saying.
Scala did very well then, judging by your words. I could probably offer a loose analogy to Typescript as well; while it does compile to JS underneath, they added a stricter layer that makes programming in it more deterministic and stable. (Not the same thing because my main point was "runtime" but hey, show me a perfect analogy.)
You are free to say your last sentence. I am free to disagree. My practice has shown me that runtimes bleed into syntax almost always. Exceptions exist, sure.
weavejester 9 hours ago [-]
But syntax must necessarily include what it's representing, no? For instance, `{:a 1}` represents an immutable map in Clojure, in the same way that `42` represents an immutable integer in Java.
pdimitar 8 hours ago [-]
Agreed, though I didn't mean "constants" when I said "immutability".
Boxxed 9 hours ago [-]
Those differences are not due to the syntax, they're due to much deeper things like the differing type system.
weavejester 8 hours ago [-]
I'm not sure I agree. Certainly there are differences other than syntax, but that doesn't mean syntax is irrelevant. For instance, would Clojure programmers use maps as much if there was no syntax for map literals?
Syntax determines what parts of a language are within easy reach, and therefore affects how programmers use the language. Tools that a syntax make easy are used often; tools that syntax makes hard are used infrequently. This indirectly impacts how a piece of software is designed.
acdw 7 hours ago [-]
This is very much what I meant in the post (hi, I'm the author :P)! CL has maps, but they're a pain to use - not just because of the syntax, but because of the relative dearth of standard library functions to work with them compared to say, lists or even vectors.
Are JVM virtual threads not on par with golangs's concurrency? I think core.async even uses virtual threads now
pdimitar 12 hours ago [-]
If they are, I have not heard about it (which does not mean much, I check Java once a year). And if they really are then I'd give Java a serious look again because it's a mature ecosystem that was gimped by ancient runtime decisions for literal decades.
cogman10 11 hours ago [-]
As of Java 24 (Java 25 being an LTS) I'd say they are equivalent. You can use a virtual thread just like you use a regular thread and there's basically no handicaps or gotchas. In Java 21, when they were released, there is a gotcha that the pretty normal use of the `synchronized` keyword would pin a "carrier thread" which ends up blocking all virtual threads from running on that carrier thread.
Pinning can still happen in some much more rare cases, same with go. For example, FFI.
The memory usage, performance, etc are all go like. You can spawn millions of virtual threads with hardly and memory requirements and without overburdening the OS with context switches. The JVM also enjoys faster GC performance with virtual threads.
raspasov 9 hours ago [-]
^^^ This.
vips7L 7 hours ago [-]
Your knowledge is outdated. Go check again.
raspasov 9 hours ago [-]
What can the Erlang / Golang runtimes do that the JVM can’t?
pdimitar 9 hours ago [-]
Thousands of share-nothing actors (fibers / green-threads) with first-class support for communication between them, for a start. Erlang/Elixir -- immutability as well.
lgrapenthin 8 hours ago [-]
"As a rule of thumb, if your application never has 10,000 virtual threads or more, it is unlikely to benefit from virtual threads."
Obviously. But it's really nice to have the option, and none of us knows the future. I've been bitten by those "0.1% chance" things much more times than I would be not-embarrassed to admit, and I know I a not alone.
whaleofatw2022 7 hours ago [-]
BEAM threads are kinda magicsauce tho, instructions have a cost and after a certain cost total (quantums) the scheduler can divert to another virt thread to guarantee forward progress. Also the immutability rules etc make it easier to optimize this switching.
rashkov 8 hours ago [-]
What kind of software actually requires this? Honest question. Anything I can think of would probably be written by C++ devs
RossBencina 6 hours ago [-]
"requires" is of course subjective, there are always multiple ways to do something. But sometimes it is convenient to model a system as concurrent execution streams, for example: multiple sessions (servers), multiple entities (games, robotics), multiple in-flight transactions (any kind of i/o or concurrent compute). Agreed these are often C++ use-cases but there are obvious benefits to using Erlang or other virtual machines: memory safety, isolation, fault tolerance.
pdimitar 8 hours ago [-]
Web / API services during bursts. Or just when you _really_ don't want to scale horizontally.
Elixir / Golang can do this very well. And they do. I have supervised, led and authored such projects that are in production to this day.
Rust too but it's lower-level and you kind of have to hand-roll OTP which of course will always fail.
midnight_eclair 2 hours ago [-]
from experience, during bursts it's never actual web/api server that is bogged down, it's the downstream io bottlenecks.
if your accepting layer is abstracted away and implemented correctly, there is very little performance difference between different concurrency approaches and all you're exposed to as developer is implementation of your handler functions.
vips7L 7 hours ago [-]
Virtual threads can do that too.
Barrin92 7 hours ago [-]
>share-nothing actors
although this is a deliberate choice rather than some accidental defect. Clojure went with STM as its concurrency model, if you're not buying into that and you want an Actor-centric language it's not the right choice to begin with.
funcDropShadow 1 hours ago [-]
STM is seldom used in modern Clojure projects, it is certainly not the dominant model. Most projects I am aware of use a few or even exactly one atoms with immutable data structures.
6 hours ago [-]
Thaxll 9 hours ago [-]
Low memory usage.
agambrahma 12 hours ago [-]
Yeah, the content + feel felt like I'm reading this in 2013.
Nothing wrong with that, it's a good thing that stuff is discovered anew [as opposed to being lost/forgotten], but it did bring a smile to me.
beders 8 hours ago [-]
Good thing you have a variety of those nowadays.
Clojure runs in the browser, Node.js, cross-compiles to Dart, works stand-alone via babashka and has a brand new C++ interfacing implementation in Jank.
The ergonomics of using a proper REPL and interactive programming is hard to beat.
5 hours ago [-]
dundunUp 8 hours ago [-]
Yeah, the content + feel felt like I'm reading this in 2013.
TacticalCoder 10 hours ago [-]
> Programming language syntax scarcely matters. It does to some extent but we the programmers tend to over-romanticize it. The runtime and its properties are the much better thing to optimize for.
But that really depends on what you're doing. For example if I'm not mistaken Amazon was run for a very long time on a Java backend. And so was GMail's backend (and back then GMail's frontend was, IIRC, Java converted to JavaScript using GWT).
And by "early Amazon" and "early GMail", we're already talking about massive scale. It's not as if the JVM got worse since then (as someone commented: a recent addition is that Clojure now use Java's virtual threads) and it's not as if it didn't scale.
So I'd say having Clojure on top of Java (for those using that Clojure: there's also ClojureScript, babashka, etc.) ain't really a problem, as long as you're fine with the occasional Java stacktrace and Java ecosystem (GP mentions that btw: that he's not familiar with Java and that, I think, can be a bit of an issue).
I'm not sure Clojure is about it's syntax: I like the focus on immutability / pure functions and I do really dig the REPL a huge lot. In addition to that something has to be said as to the incredible stability of the language and many of its libraries.
The big value add to me is that I can have a REPL and inspect, in dev (or in prod but that'd be wild), the app I'm working on. And manipulate it: redefining variables and functions etc. And it's not some hacky hot-reloading bolted on as an afterthought kludge: it's a real Lisp REPL. There's value in that IMO.
pdimitar 9 hours ago [-]
Elixir has all that _and_ Erlang OTP's amazing guarantees. Hence I landed on it.
Elixir also offers LiveBooks i.e. you can create pre-made recipes with which you directly remote into your staging / prod and do stuff.
All that with immutability and potentially 6 digits of actors / green threads with a share-nothing architecture.
---
RE: early Amazon / Google, sure. They made do with what they had and it was and still is a heroic effort. But can we agree that they succeeded _despite_ the numerous warts and defects of the PLs and their runtimes at the time? Not _because_ of them?
I feel that people latch onto the misleading "they succeeded with language X and are big, hence the language X is great" thing way too often. No. It's not true. The only thing that follows from "big company A made it big with language X" is: "company A has an amazing engineering team". Nothing else.
bsder 8 hours ago [-]
> Programming language syntax scarcely matters.
Certainly it matters much less in the modern era.
However, certain fundamental decisions of a language can be dealbreakers.
Requiring declarations on your functions and giving those declarations sigils so that they can be parsed quickly is an important syntax decision. Almost every modern programming language has converged to this idea.
Or take, for example, Lua. For me, personally, the 1-based-ness of Lua is simply a dealbreaker no matter how good anything else about it is.
For the "Lisps", I LOATHE the fact that you traverse lists and vectors in completely different ways--you can't just drop any container-ish thing into something that iterates/collects it. This is something that both Clojure and Racket seem to agree on--you have something that acts like a "collection" and you can walk across it the same way regardless of the specific type of collection it is. Of course, that is why a bunch of Lisp purists loathe Clojure and Racket while I like those languages. Shrug.
I find RAII (Resource acquisition is initialization) to be the source of all things evil if it infests a programming language. The popularity of C++ and Rust speaks to the quite large number of people who think my opinion is bullshit.
So, yeah, base syntax matters far less than it used to. But the engineering decisions that went into making that syntax correspondingly are far more important.
adrian_b 2 hours ago [-]
I agree with most of what you said, but I am puzzled about your claim about RAII.
Whether it is good for any kind of resource acquisition to look the same like an initialization is debatable.
On the other hand, I cannot see any counterargument to the principle that releasing any resources should not be done explicitly, but only implicitly, upon leaving a block (using reference counts for shared resources).
I doubt that you advocate for the use of explicit release commands for resources, which are a notorious source of bugs, so what is that you consider as not being the same as RAII?
RAII was a not very useful acronym that was just another form to say that the C or C++ programmers should never use the PL/I style of explicit free commands, despite the availability of functions like "free()" or "close()" in the standard library, but both memory and files and any other kinds of resources should be managed with automatic releasing.
I do not see how this sound principle can infest any language.
Obviously, I have seen examples of bad RAII implementations, like I have seen examples of misuse for any other programming principle.
pdimitar 8 hours ago [-]
I think we can easily agree they are two entities (syntax / runtime) that feed off of each other. And I do agree that previously the syntax mattered more.
(I very much agree on Lua btw.)
Personally I am very disheartened. Surely algebraic data types should be universally a good thing and all PLs should gradually adopt them? But no, endless HN / Reddit threads bike-shedding.
Oh well.
tartoran 7 hours ago [-]
Too Many Requests
The page you have tried to access is not available because the owner of the file you are trying to access has exceeded our short term bandwidth limits. Please try again shortly.
Details:
Actioning this file would cause "www.acdw.net//clojure/" to exceed the per-day file actions limit of 80000 actions, try again later
> I do wish there were an easier way to move in the ]}]})))}-ness of block ends though.
I’m not quite sure what this means. How is it different/worse than all parens..?
fyi I use paredit and just hit ) and it moves me past any kind of paren/bracket. But even without that you can just hit left and right..?
acdw 7 hours ago [-]
I use Emacs's built-in structural editing bindings, which doesn't have the auto-move-past any kind of paren/bracket thing. Maybe I could add that in.
But I was talking about like, when refactoring, I'll maybe change something from a list to a vector, and I have to change the delimiters at front and back. Or, where electric-pair does do the move-past-all-parens thing when I just spam ), it doesn't do that with ]}]}]]}}]})).
tmtvl 48 minutes ago [-]
I don't recall the exact name, but I believe it's forward-up-sexp which allows you to jump forward past the next closing delimiter. Though if I'm not mistaken only the backwards one has a default binding.
meken 5 hours ago [-]
I see.
Have you ever tried paredit? It’s pretty much a lifesaver for this kind of thing.
everforward 7 hours ago [-]
They are a pain if they get unbalanced if you aren’t using paredit. Like if I vi delete the last line of a function out of habit it’s a pain to get them back in the right order.
It’s easier if everything is parens, just hit paren til the errors are gone.
meken 5 hours ago [-]
Ah I see. That makes sense.
That’s making me really thankful to be a paredit user.
drob518 9 hours ago [-]
Exactly. Paredit for the win.
pjmlp 4 hours ago [-]
> Clojure makes a big point of being a hosted language, that is, a language that runs on a premade runtime.
This is why I am found of the community, the symbiotic approach of two language communities working together.
gertlabs 13 hours ago [-]
The functional paradigm is a bit uncomfortable at first, but it does make problem solving feel... different. I personally find OOP to be the most intuitive for large scale systems design, but that's just me.
Most models do not perform particularly well in Clojure, but OpenAI models fully utilize the power of the language. Subjectively, it kind of seems to match the personality. Data at https://gertlabs.com/rankings?provider=openai
gleenn 12 hours ago [-]
> I personally find OOP to be the most intuitive for large scale systems design, but that's just me.
The beauty of Clojure shines through when you want to change something that cuts through a large part of a large project. If you are using mutable data, you may end up with many bugs from various pieces of code mutating objects inconsistently. With Clojure, if someone hands you data, you can't possibly break some distant piece of code by updating an object: it's just not possible because you only ever make fast, updated copies. The more complicated your codrbase gets, the more this benefit is realized.
I actually kind of think of it as an easier mechanism with similar outcomes to Rust's borrow checker. Only one piece of code ever owns the data so things end up much safer. However it is way easier to use IMHO because you just know that zero people own anything and everyone can read everything.
It also makes converting some code to be multi-threaded extremely easily and with some constraints guaranteeably correct.
Lots of dovetailing features neatly put together for both clarity and less bugs and more usable cores which are probably sitting idle.
schonfinkel 11 hours ago [-]
> I personally find OOP to be the most intuitive for large scale systems design, but that's just me.
Once you're more comfortable with it and want to try a typed functional programming language, I highly recommend checking OCaml (or SML, if you're into old school tech) and see how the Module Functors are applied, most software will look extremely over-engineered after you write a few functors. It's the feature I miss the most when coding in F# or Gleam, for instance.
chamomeal 12 hours ago [-]
Raw models aren't as effective with clojure as they are with typescript or python, but clojure has a superpower that most other languages don't have: the REPL! Specifically nREPL and the ecosystem around it.
An LLM is only as good as its feedback loop. If your LLM can actually test the code it writes, it's going to be much more effective. Static types are a form of feedback (if it can use the LSP), unit/integration tests are another.
Clojure has an exceptionally good repl. LLMs can eval any piece of any function. They can test out functions they aren't familiar with. They can fetch data, try out different arguments, try different approaches before committing to one. They can query a database (read-only connection, of course), look at the result, fetch data from an API, and stitch it all together. It can even hook into your running program and debug it from the inside out!!
It makes it so much more effective at using libraries or paradigms that it isn't trained on. In my experience, hooking an LLM up to the clojure repl lets it write WAY more complex stuff. I'm talking like 10x more complex programs with zero errors, cause it can literally try it out every little piece before putting it together. It's like watching a human programming. But like, really fast.
Sorry I get a little ranty when clojure + LLMs come up, because I don't think most people realize what they're missing out on. It's crazy stuff. It's also easy peasy if you use vscode. There's an extension called calva-backseat-driver that just hooks it all up for you. Gives copilot access to the repl, and I think it exposes an mcp if you want to give claude access too.
gertlabs 12 hours ago [-]
GPT 5.4+ models are extremely good at writing Clojure, agreed. In the agentic coding part of our benchmark, they do have access to the REPL via bash if they choose to use it. Filtered here: https://gertlabs.com/rankings?mode=agentic_coding
Hammershaft 5 hours ago [-]
It's fascinating that Clojure has consistently the best performing solutions and yet at the same time such a low success rate.
Do you have an idea as to why that is?
If I had to guess, two things lowering reliability:
A) Balancing parens might be tough on an LLM one-shot.
B) LLMs generate tokens sequentially, but s-expressions mean the first forms to be evaluated in a body are usually the last to be written, so the LLM has to sequentially generate layers of evaluation backwards.
jwr 3 hours ago [-]
First, we would need to agree on what "such a low success rate" means. Programmers have a thundering herd mentality: there are usually 2-3 "top things" that are in fashion at any given time and the herd tends to go towards these top things. They are not necessarily good or "successful" (however you define that term), they are just popular today.
From my point of view, Clojure is a very successful language. It has been in stable development for >10 years now, with no major breaking changes (!). I was able to start a business using it and now make a living from it, all of it possible largely because Clojure reduces incidental complexity so much.
Now, as to LLMs, I can see this discussion is mostly theoretical, so let me pitch in with data. I've been using LLMs for Clojure for a while now and it works fantastically, from what I read about other languages, quite a bit better for me than for others. Balancing parens was a problem for early LLMs without tools, Claude Opus with clojure-mcp tools doesn't encounter that problem at all.
Additionally, the ability to try things in the REPL means that LLMs are very effective: all hypotheses and solutions are immediately tested, with automatic feedback.
Overall I get great value from LLMs and I am able to solve large problems with them.
2 hours ago [-]
xoxolian 4 hours ago [-]
If B), then maybe the LLM should be instructed to prefer things like the -> and ->> operators. So the first forms evaluated are also the first written.
xoxolian 12 hours ago [-]
Thanks for the link!
What would you say is missing from Clojure for large-scale OOP design? As I understand, Clojure gives you OOP a la carte. Objects (via maps/records/structs), polymorphic dispatch (via multimethods/protocols/case), types (via Malli/TypedClojure), inheritance (via derived, isa?, etc), some encapsulation (via defn-/^:private)...
chamomeal 12 hours ago [-]
Not the person you're replying to, but have you tried TypedClojure? I've always thought clojure-with-types would literally be the perfect language, but I also read TypedClojure is more of a research project than a real language that you should use in prod.
xoxolian 11 hours ago [-]
No sorry, of the things I've listed, I'd never seen nor heard of a project that uses Typed Clojure, nor probably inheritance via dervied/isa?.
For static (partial) typing, I instead use Malli schemas. I do this for every larger Clojure program I make, because there's always something that needs paranoia, or it's handy to generate example data.
gertlabs 12 hours ago [-]
I might just be a simpleton -- I never had the resolve to try an ambitious project in Clojure. I was not aware that you could get full OOP though, what you are describing feels like yes technically possible but kind of a hack to get inheritance / no type hierarchy enforcement. I'm no expert on the language though
bcrosby95 12 hours ago [-]
I actually disagree. Once you remove the cruft and crap of the involved syntax, good OOP design tends to look damn close to FP design. So I flip your point of view - class based OOP is the hack - despite not really using Clojure or FP in my dayjob or hobby projects anymore. Most fun I had with OOP was definitely Common Lisp though.
drob518 9 hours ago [-]
> I personally find OOP to be the most intuitive for large scale systems design, but that's just me.
At one point, I was the same. But after going functional in Clojure, I can’t imagine going back. Using maps nd just having common functions that transform data into different data is definitely the way to go. This is with your time:
https://youtu.be/aSEQfqNYNAc
andai 12 hours ago [-]
Not sure if I'm reading this right, but the "success rate" table for OpenAI models shows Clojure near the bottom. And if I switch provider to Anthropic, success rate for most languages, including Clojure, goes up dramatically.
gertlabs 12 hours ago [-]
Success rate includes syntax/compilation failures as well as environment rule violations, and is almost entirely from one-shot code generations. Percentile shows how well the working submissions perform.
In long horizon agentic coding evaluations, strong models fix the syntax and percentile and it becomes a direct comparison of which submissions per language performed the best on average. You can filter for that here: https://gertlabs.com/rankings?provider=openai&mode=agentic_c...
jimbokun 9 hours ago [-]
Functional is far more comfortable to me. Trying to model all that state spread through out your program with no way to really isolate it or just reason about a small part of the program at a time, I find very stressful.
I found this to be one of the more interesting talks I've watched.
Like you (I think) - I love functional languages.
But there's a problem I can't really figure out how to articulate where they reach a level where they stop "just working" imo. Maybe it's just me being too dumb.
;; This is real syntax!
(loop for k being the hash-keys
using (hash-value v) of hash-table
...)
Still lisp. Although the blog author has a point -
clojure is probably cleaner lisp than common lisp.
I think the issue is heavily due to syntax though.
Naturally the (())()()()(), but I think even aside
from the (), the syntax does not seem super-efficient
to me. Perhaps I have spent too much time with ruby
and python, but it feels as if lisp is a legacy
regression, purely syntax-wise.
NetMageSCW 12 hours ago [-]
I wonder if the author is familiar with Smalltalk - it has a very small syntax. In some ways so does Lisp, in other ways it has more than every other language, depending on what you think about operators versus functions.
acdw 7 hours ago [-]
I have heard of Smalltalk and it is intriguing but honestly I don't even know how to get started with it lol
roshanroyj 4 hours ago [-]
I am able to see the link just fine.
smitty1e 12 hours ago [-]
"Lua: everything is a table
Tcl: everything is a string
Lisp: everything is a list"
Python: {"everything":"dictionary"}
zuzululu 9 hours ago [-]
Thats nice but not many jobs for it
jimbokun 9 hours ago [-]
If that’s your concern you don’t need to be concerned with any technical quality of any language. Just count job postings by language and learn the one with the highest value.
zuzululu 9 hours ago [-]
if there isn't any jobs for it it probably doesn't have the appeal or communicate economic value
Hammershaft 5 hours ago [-]
Clojure programmers tend to be some of the highest paid[1] on average of any language, so I'd lean more towards a lack of appeal.
Other than that, I agree, CL is baroque yet needs some hole filling here and there.
> Lisp: everything is a list
But that's wrong. Not even a little. Unless you mean LISP 1.5...
> Too much syntax
Funnily, I'm mostly okay with the new vector/set/hash-table literals, my big problem and that of some other people is the use of vectors in macros/special operators instead of lists. `(let [a b] ...)` instead of `(let (a b) ...)` is _not_ okay.
midnight_eclair 53 minutes ago [-]
> `(let [a b] ...)` instead of `(let (a b) ...)` is _not_ okay
it is however quite consistent, clojure uses vector form for most macros that require a "control" form before a "data" form: argument list in defn, names list in let, iteration descriptors in for, etc. you get used to consistency quite easily.
y1n0 12 hours ago [-]
I haven’t used clojure in quite a while but what’s the issue with (let [a b] …)?
Is (let (a b) …) even valid clojure?
acdw 7 hours ago [-]
In CL and Scheme, it's (let ((var1 val) (var2 val)) body...).
So parentheses are used for grouping and function/macro application.
In Clojure, parens are just used for application, so you have e.g.
(let [var1 val var2 val] body...), or (defn foo [x] ..) or (cond testa 1 testb 2 ...).
It takes some getting used to, and I do wish Clojure would do something more like
(let [[var1 val] [var2 val]] ... .. though of course then you'd have to figure something else out for destructuring.
everforward 12 hours ago [-]
I believe it would be (let ‘(a b)), but I’m not sure if that’s valid or not. That’s how Racket does its version of defn
If he means navigating the AST, there is Parinfer: https://shaunlebron.github.io/parinfer/
Paredit / Parinfer ruined other languages for me. It lets you navigate up/down/in/out of the Clojure AST with keyboard commands and mutate those expressions, e.g. "Split" will split open the current data structure you're in: `(a| b)` =Split=> `(a)| (b)`, where | is caret. Join is the inverse, and it works for all data structures.
As everyone knows, you are not a true lisper until you have written your own static site generator.
It gave me such a great high with how easy it was to add my own "templating engine" on top, implemented all using macros. The downside is that the crash came hard; there is so much more to a good static site generator such as optimizing the output, supporting scoped CSS, server-side rendering of SPA framework components, and of course integration with the Node ecosystem (for better or for worse there is just so much useful stuff). I have since moved over to Astro. It's still fascinating how far I was able to push my own SSG all by myself though.
I think that part is quite normal. I use ruby for the same purpose, though the only difference is that the code I use is also to be used for dynamic websites at the same time (cgi, rack, sinatra, in theory ruby on rails but I just can't stand rails and DHH these days, so I am in the opposition crowd). Using static websites, though, always feel as if I have significantly less flexibility. I do generate some static .html files as well, but they feel less useful to me, aside from being displayed faster, of course.
For the most part, any Clojure code which doesn't use host interop will work on all dialects. Clojure also has support for conditional code, depending on the current dialect.
This is one of Clojure's superpowers.
Currently, we have Clojure, ClojureScript, ClojureCLR, Babashka, Basilisp, Phel, and jank running the test suite.
I have only used Clojure, ClojureScript, and Babashka in production. But I am the creator of jank.
I too liked Clojure when I tried it some years ago (agreed on the composition and data structures; both are _great_). But the real value-add is in the runtime, not the syntax. Java has a solid runtime but it's not yet as good as Erlang's, maybe even not up to the standards of Golang -- I am talking concurrency / parallelism here (for memory management I have no doubts Java is very good). And I know: green threads and stuff. Well, call me when you can do what Erlang / Golang can do. Then I'll look again, very seriously too.
Programming language syntax scarcely matters. It does to some extent but we the programmers tend to over-romanticize it. The runtime and its properties are the much better thing to optimize for.
LISP is much more than just a runtime syntax, such as its distinct evaluation model and metalinguistic core.
The JVM was chosen for Clojure because of its reach and vast ecosystem. People have ported Clojure to other runtimes, even Beam (Clojerl), where it enjoys decent success, too.
Still want Erlang's runtime though i.e. the many green threads with share-nothing architecture that can communicate with each other.
won't lie, this is hilarious. you got me from nodding along to being the spitting out food meme guy in a span of couple seconds.
JVM runtime is undeniably the most well researched and optimized runtime in history of runtimes, specifically in realm of concurrency and parallelism, it literally carries like half the world on it's back.
not to throw any shade on erlang vm - i've been a fan for well more than a decade, but other than making some interesting, but limited in practice, tradeoffs with regard to concurrency architecture, it doesn't really offer much more.
go's runtime is just a different beast altogether designed with different goals in mind and with no baggage of backward compatibility with legacy.
one particular detail i'm very grateful to Clojure for, is exactly the ability to use JVM runtime without having to touch any Java.
> Programming language syntax scarcely matters
on the contrary, it matters quite a lot.
you might be drinking some of that AI koolaid, conflating our suddenly hypertrophied abilities to produce code regardless of our familiarity with the syntax or the APIs with ability to produce and deliver good quality products, but this delusion is getting reality check as we speak.
a realization is propagating through the industry that being able to produce more code than you're able to review, comprehend and internalize is actually not a great thing.
and that's where syntax matters - it has to be high signal/noise, it has to expose you to right abstractions and it has to be pliable to allow the codebase reflect the problem in a way that minimizes cognitive load both during production and during consumption.
LLMs are language models and syntax is a crucial part of any language.
[0]: https://en.wikipedia.org/wiki/LFE_(programming_language)
Love the idea of LFE but it needs a bigger ecosystem.
Clojure brings more than syntax though... there's an opinionated take on making all data structures immutable (as in, structural sharing [1]) by default. That's a huge difference in how you architect the program and debug it.
[1] https://softwarepatternslexicon.com/clojure/core-concepts-of...
I always wished clojerl took off.
It has footguns, sure, but with library support and discipline it can get you very far.
To me it's embarrassing that PLs still tout syntax and various other goodies, completely glossing over runtime. I might be missing something. But faux humble statements aside, I feel many others are the ones who miss something -- and that's the fact that doing stuff in parallel is a fact of life for 20+ years now and it's time all popular PL runtimes finally wake up to that fact.
If not, I am simply not considering them. And I am not saying that arrogantly though it sounds that way; there are some PLs that I _really_ liked and was almost heart-broken that I had to abandon them and not work professionally with them. But I have enough experience to know that runtime choice matters, a lot.
For the record, Racket was one of those PLs I abandoned. I know they started working on parallelism some years ago but I had to make a decision next week back then so, Elixir + Golang + Rust it is for me.
I'm not sure I understand this argument. Java and Clojure share a runtime, but an idiomatic Java codebase is going to have a very different architecture and design to an idiomatic Clojure codebase. Conversely, a codebase written in Go may end up looking very similar to a codebase written in Java, despite using different runtimes.
As mentioned, I did like Clojure. I'd switch to it if it was running inside the Erlang runtime (like Elixir does).
F.ex. if you have an universal construct of green threads / fibers then 7 PLs could express it 7 different ways, yet underneath they'd all be the same.
So focusing on the runtime's guarantees doesn't seem like a practicality focused argument to me.
Furthermore, if you trace my comments, you'll see that I had to choose PLs years ago (12+ to be precise). Things were quite different at the time. Java might have almost caught up today; back then we couldn't even be certain `synchronized` is stable all the time. Just saying.
Scala did very well then, judging by your words. I could probably offer a loose analogy to Typescript as well; while it does compile to JS underneath, they added a stricter layer that makes programming in it more deterministic and stable. (Not the same thing because my main point was "runtime" but hey, show me a perfect analogy.)
You are free to say your last sentence. I am free to disagree. My practice has shown me that runtimes bleed into syntax almost always. Exceptions exist, sure.
Syntax determines what parts of a language are within easy reach, and therefore affects how programmers use the language. Tools that a syntax make easy are used often; tools that syntax makes hard are used infrequently. This indirectly impacts how a piece of software is designed.
Pinning can still happen in some much more rare cases, same with go. For example, FFI.
The memory usage, performance, etc are all go like. You can spawn millions of virtual threads with hardly and memory requirements and without overburdening the OS with context switches. The JVM also enjoys faster GC performance with virtual threads.
https://docs.oracle.com/en/java/javase/21/core/virtual-threa...
Elixir / Golang can do this very well. And they do. I have supervised, led and authored such projects that are in production to this day.
Rust too but it's lower-level and you kind of have to hand-roll OTP which of course will always fail.
if your accepting layer is abstracted away and implemented correctly, there is very little performance difference between different concurrency approaches and all you're exposed to as developer is implementation of your handler functions.
although this is a deliberate choice rather than some accidental defect. Clojure went with STM as its concurrency model, if you're not buying into that and you want an Actor-centric language it's not the right choice to begin with.
Nothing wrong with that, it's a good thing that stuff is discovered anew [as opposed to being lost/forgotten], but it did bring a smile to me.
The ergonomics of using a proper REPL and interactive programming is hard to beat.
But that really depends on what you're doing. For example if I'm not mistaken Amazon was run for a very long time on a Java backend. And so was GMail's backend (and back then GMail's frontend was, IIRC, Java converted to JavaScript using GWT).
And by "early Amazon" and "early GMail", we're already talking about massive scale. It's not as if the JVM got worse since then (as someone commented: a recent addition is that Clojure now use Java's virtual threads) and it's not as if it didn't scale.
So I'd say having Clojure on top of Java (for those using that Clojure: there's also ClojureScript, babashka, etc.) ain't really a problem, as long as you're fine with the occasional Java stacktrace and Java ecosystem (GP mentions that btw: that he's not familiar with Java and that, I think, can be a bit of an issue).
I'm not sure Clojure is about it's syntax: I like the focus on immutability / pure functions and I do really dig the REPL a huge lot. In addition to that something has to be said as to the incredible stability of the language and many of its libraries.
The big value add to me is that I can have a REPL and inspect, in dev (or in prod but that'd be wild), the app I'm working on. And manipulate it: redefining variables and functions etc. And it's not some hacky hot-reloading bolted on as an afterthought kludge: it's a real Lisp REPL. There's value in that IMO.
Elixir also offers LiveBooks i.e. you can create pre-made recipes with which you directly remote into your staging / prod and do stuff.
All that with immutability and potentially 6 digits of actors / green threads with a share-nothing architecture.
---
RE: early Amazon / Google, sure. They made do with what they had and it was and still is a heroic effort. But can we agree that they succeeded _despite_ the numerous warts and defects of the PLs and their runtimes at the time? Not _because_ of them?
I feel that people latch onto the misleading "they succeeded with language X and are big, hence the language X is great" thing way too often. No. It's not true. The only thing that follows from "big company A made it big with language X" is: "company A has an amazing engineering team". Nothing else.
Certainly it matters much less in the modern era.
However, certain fundamental decisions of a language can be dealbreakers.
Requiring declarations on your functions and giving those declarations sigils so that they can be parsed quickly is an important syntax decision. Almost every modern programming language has converged to this idea.
Or take, for example, Lua. For me, personally, the 1-based-ness of Lua is simply a dealbreaker no matter how good anything else about it is.
For the "Lisps", I LOATHE the fact that you traverse lists and vectors in completely different ways--you can't just drop any container-ish thing into something that iterates/collects it. This is something that both Clojure and Racket seem to agree on--you have something that acts like a "collection" and you can walk across it the same way regardless of the specific type of collection it is. Of course, that is why a bunch of Lisp purists loathe Clojure and Racket while I like those languages. Shrug.
I find RAII (Resource acquisition is initialization) to be the source of all things evil if it infests a programming language. The popularity of C++ and Rust speaks to the quite large number of people who think my opinion is bullshit.
So, yeah, base syntax matters far less than it used to. But the engineering decisions that went into making that syntax correspondingly are far more important.
Whether it is good for any kind of resource acquisition to look the same like an initialization is debatable.
On the other hand, I cannot see any counterargument to the principle that releasing any resources should not be done explicitly, but only implicitly, upon leaving a block (using reference counts for shared resources).
I doubt that you advocate for the use of explicit release commands for resources, which are a notorious source of bugs, so what is that you consider as not being the same as RAII?
RAII was a not very useful acronym that was just another form to say that the C or C++ programmers should never use the PL/I style of explicit free commands, despite the availability of functions like "free()" or "close()" in the standard library, but both memory and files and any other kinds of resources should be managed with automatic releasing.
I do not see how this sound principle can infest any language.
Obviously, I have seen examples of bad RAII implementations, like I have seen examples of misuse for any other programming principle.
(I very much agree on Lua btw.)
Personally I am very disheartened. Surely algebraic data types should be universally a good thing and all PLs should gradually adopt them? But no, endless HN / Reddit threads bike-shedding.
Oh well.
The page you have tried to access is not available because the owner of the file you are trying to access has exceeded our short term bandwidth limits. Please try again shortly. Details:
Actioning this file would cause "www.acdw.net//clojure/" to exceed the per-day file actions limit of 80000 actions, try again later
I’m not quite sure what this means. How is it different/worse than all parens..?
fyi I use paredit and just hit ) and it moves me past any kind of paren/bracket. But even without that you can just hit left and right..?
But I was talking about like, when refactoring, I'll maybe change something from a list to a vector, and I have to change the delimiters at front and back. Or, where electric-pair does do the move-past-all-parens thing when I just spam ), it doesn't do that with ]}]}]]}}]})).
Have you ever tried paredit? It’s pretty much a lifesaver for this kind of thing.
It’s easier if everything is parens, just hit paren til the errors are gone.
That’s making me really thankful to be a paredit user.
This is why I am found of the community, the symbiotic approach of two language communities working together.
Most models do not perform particularly well in Clojure, but OpenAI models fully utilize the power of the language. Subjectively, it kind of seems to match the personality. Data at https://gertlabs.com/rankings?provider=openai
The beauty of Clojure shines through when you want to change something that cuts through a large part of a large project. If you are using mutable data, you may end up with many bugs from various pieces of code mutating objects inconsistently. With Clojure, if someone hands you data, you can't possibly break some distant piece of code by updating an object: it's just not possible because you only ever make fast, updated copies. The more complicated your codrbase gets, the more this benefit is realized.
I actually kind of think of it as an easier mechanism with similar outcomes to Rust's borrow checker. Only one piece of code ever owns the data so things end up much safer. However it is way easier to use IMHO because you just know that zero people own anything and everyone can read everything.
It also makes converting some code to be multi-threaded extremely easily and with some constraints guaranteeably correct.
Lots of dovetailing features neatly put together for both clarity and less bugs and more usable cores which are probably sitting idle.
Once you're more comfortable with it and want to try a typed functional programming language, I highly recommend checking OCaml (or SML, if you're into old school tech) and see how the Module Functors are applied, most software will look extremely over-engineered after you write a few functors. It's the feature I miss the most when coding in F# or Gleam, for instance.
An LLM is only as good as its feedback loop. If your LLM can actually test the code it writes, it's going to be much more effective. Static types are a form of feedback (if it can use the LSP), unit/integration tests are another.
Clojure has an exceptionally good repl. LLMs can eval any piece of any function. They can test out functions they aren't familiar with. They can fetch data, try out different arguments, try different approaches before committing to one. They can query a database (read-only connection, of course), look at the result, fetch data from an API, and stitch it all together. It can even hook into your running program and debug it from the inside out!!
It makes it so much more effective at using libraries or paradigms that it isn't trained on. In my experience, hooking an LLM up to the clojure repl lets it write WAY more complex stuff. I'm talking like 10x more complex programs with zero errors, cause it can literally try it out every little piece before putting it together. It's like watching a human programming. But like, really fast.
Sorry I get a little ranty when clojure + LLMs come up, because I don't think most people realize what they're missing out on. It's crazy stuff. It's also easy peasy if you use vscode. There's an extension called calva-backseat-driver that just hooks it all up for you. Gives copilot access to the repl, and I think it exposes an mcp if you want to give claude access too.
Do you have an idea as to why that is?
If I had to guess, two things lowering reliability:
A) Balancing parens might be tough on an LLM one-shot.
B) LLMs generate tokens sequentially, but s-expressions mean the first forms to be evaluated in a body are usually the last to be written, so the LLM has to sequentially generate layers of evaluation backwards.
From my point of view, Clojure is a very successful language. It has been in stable development for >10 years now, with no major breaking changes (!). I was able to start a business using it and now make a living from it, all of it possible largely because Clojure reduces incidental complexity so much.
Now, as to LLMs, I can see this discussion is mostly theoretical, so let me pitch in with data. I've been using LLMs for Clojure for a while now and it works fantastically, from what I read about other languages, quite a bit better for me than for others. Balancing parens was a problem for early LLMs without tools, Claude Opus with clojure-mcp tools doesn't encounter that problem at all.
Additionally, the ability to try things in the REPL means that LLMs are very effective: all hypotheses and solutions are immediately tested, with automatic feedback.
Overall I get great value from LLMs and I am able to solve large problems with them.
What would you say is missing from Clojure for large-scale OOP design? As I understand, Clojure gives you OOP a la carte. Objects (via maps/records/structs), polymorphic dispatch (via multimethods/protocols/case), types (via Malli/TypedClojure), inheritance (via derived, isa?, etc), some encapsulation (via defn-/^:private)...
For static (partial) typing, I instead use Malli schemas. I do this for every larger Clojure program I make, because there's always something that needs paranoia, or it's handy to generate example data.
At one point, I was the same. But after going functional in Clojure, I can’t imagine going back. Using maps nd just having common functions that transform data into different data is definitely the way to go. This is with your time: https://youtu.be/aSEQfqNYNAc
In long horizon agentic coding evaluations, strong models fix the syntax and percentile and it becomes a direct comparison of which submissions per language performed the best on average. You can filter for that here: https://gertlabs.com/rankings?provider=openai&mode=agentic_c...
I found this to be one of the more interesting talks I've watched.
Like you (I think) - I love functional languages.
But there's a problem I can't really figure out how to articulate where they reach a level where they stop "just working" imo. Maybe it's just me being too dumb.
I think the issue is heavily due to syntax though. Naturally the (())()()()(), but I think even aside from the (), the syntax does not seem super-efficient to me. Perhaps I have spent too much time with ruby and python, but it feels as if lisp is a legacy regression, purely syntax-wise.
Tcl: everything is a string
Lisp: everything is a list"
Python: {"everything":"dictionary"}
[1] https://survey.stackoverflow.co/2019#technology-_-what-langu...
do a search for indeed jobs
Eh? That's completely lifted from CL (https://www.lispworks.com/documentation/HyperSpec/Body/t_seq...). Same for AREF/NTH, there's ELT.
Other than that, I agree, CL is baroque yet needs some hole filling here and there.
> Lisp: everything is a list
But that's wrong. Not even a little. Unless you mean LISP 1.5...
> Too much syntax
Funnily, I'm mostly okay with the new vector/set/hash-table literals, my big problem and that of some other people is the use of vectors in macros/special operators instead of lists. `(let [a b] ...)` instead of `(let (a b) ...)` is _not_ okay.
it is however quite consistent, clojure uses vector form for most macros that require a "control" form before a "data" form: argument list in defn, names list in let, iteration descriptors in for, etc. you get used to consistency quite easily.
Is (let (a b) …) even valid clojure?
It takes some getting used to, and I do wish Clojure would do something more like (let [[var1 val] [var2 val]] ... .. though of course then you'd have to figure something else out for destructuring.