... and I think you will both find it intellectually stimulating to explore the complexity behind ERP systems that makes a common DSL near impossible across industries, or as businesses change.
DSL presumes agreement on semantics, and that's often the most difficult part.
- economies of scale no longer work, and you end up doing a custom ERP for your business from scratch.
- your business changes might invalidate your model quickly. You sell through distributors, but open an online shop -- and suddenly your customer is not one of few dozen well-known businesses with a known address and tax number, but user2252 who bought something late at night last night. And you want to understand the needs and behaviors of both.
- for the economies of scale, you might develop your custom solution 20x faster now, but you're still in competition with the established provides with templates for most of the cases (who btw have the same LLM capability at their disposal)
- for the change in business -- you can ask an LLM which changes this induces, and it will give you most typical impacts. And then you're back at square deciding if it's better to roll your custom DSL and the custom system downstream, or just use off-the-shelf stuff that covers 95% of it from day one (well, maybe day two or three)
When it comes to back office business programming, there’s just a lot of code tasked with copying a litany of bits of data from one structure to another.
Whether it’s copying a web form into a database, or converting Their JSON to Your JSON, it’s a lot of detail that does not abstract well. It’s all shapes and sizes and formats, and it almost always has to be enumerated in excruciating detail and, typically, twice.
Sure, there’s logic and whatnot involved, but it, too, is specific to some subdomain of the larger system and it, too, does not abstract well. Not in the large context of the overall system.
Accounts Payable and Accounts Receivable, at 10,000 feet look almost identical. They’re almost literally the same thing with the sign flipped. But in practice, they don’t share code well. You end up with two similar systems, but not similar enough where sharing is actually worthwhile.
At best they can leverage a common API to the GL.
Turns out a lot of languages can manifest a decent level of abstraction. But even then, folks push back.
Consider the love/hate relationship with ORMs. Or the annotation driven markup in Java programs and the underlying “magic” that they enable. Like scribing mystic runes onto things.
Those are both very powerful, yet folks experience that and toss their hands in the air and throw out the baby with the bath water and jump into something “magic free” like Go.
Just because you can use something like CL to “make your own magic”, doesn’t mean it’s a good idea. Doesn’t mean it scales. Doesn’t mean it communicates well to others. AI or no.
It’s not the AIs world yet. We already know that if the AIs want a better language suited to AI efficiency, they’ll come up with their own. I’ve already seen crass examples of “code only an AI could love”. Completely impenetrable, at least to me. May as well have represented it as a color image and collection of RGB values. Opaque to me, but the AI could “read” it.
There is much more to programming and systems than token density, and AI is still getting cheaper by the day, so less reason to even pre-optimize for it anyway.
There's lots of examples of DSLs being unreasonably useful in industry even prior to LLMs.
While I am extremely taken with Lisps and the lisp way of doing DSLs, I would probably go with an OCaml to make a DSL for a company specific ERP. It seems a better way to go about the problem.
Lisp, on the other hand, I have found to be extremely good at domains which seem the same but which are tremendously different. For example, a workout app is a surprisingly complex domain. Different exercises have different storage models and functions, as do different training sessions and different programs. Rather than try to build a monoprogram, one training app to rule them all, I find lisp wonderful for making "microprograms".
This bears resemblance to Accounts Payable and Accounts Receivable but I don't think Lisp would be a good fit for those. Perhaps a Lean or a Rocq, something with proofs.
> We already know that if the AIs want a better language suited to AI efficiency, they’ll come up with their own.
My agents seem to really like Tree Calculus and have bullied me into working on a language which uses it.
Also, in most languages an error will crash your program. So if you’re writing code with an LLM it will have to read your crash logs to make some changes and run your program again. In Common Lisp your program won’t crash, it’ll stop and open a debugger with the whole stack and all the variables. You can just point your LLM at the debugger, and it’ll make its fix and resume the program.
What does this look like in a real example system that you're maintaining? I can't imagine you'd always be able to resume like that if it's something like a webserver.
You can do this in many other interpreted languages like Python interpreter when running with --pdb flag will do the same. I imagine CL does this by default when running code in interpreted mode and omits the behaviour for compiled (production) binaries.
I mean, that entirely depends on the server design. Is it running one connection per process? One connection per thread? Presumably you've designed the server in a way that one slow connection doesn't block other connections from being handled fast, which means that this one connection whose handler went into the debugger is just now a really, really slow connection taking minutes (or hours!) to handle, but the other connections are running just fine.
Now, if your entire server is taken down because one connection threw an exception, that's bad design. But pretty much no major language works that way. All of them allow you to set things up so that an exception handling connection A won't affect connection B. And if you've done that in Common Lisp, then connection A halting and waiting for the debugger won't affect connection B either.
Maybe beside the point, but I'd hope it's neither connection per process or per thread. Modern webservers tend to rely on either an event loop or greenthreading. Looks like such a thing exists in Common Lisp, not sure how the debugger would interact with it though.
Anyway it sounds like this isn't really the target use case for the debugger since restarting a webserver is supposed to be easy. Maybe there's a different use case in mind?
no, the app isn't frozen (thinking about the Hunchentoot web server).
Also of course the default is to not get the debugger but let the server thread crash and print the backtrace. With a user setting, you can choose to get the debugger, and have this request wait (the connection may time out, which isn't an issue during development). Another setting is to print the backtrace in the browser (= dev mode).
In Python, can you fix the buggy function, re-compile it, and resume execution from the stack you want? I don't think so. That's common practice in CL, useful both during development and in production if necessary. In Python, you edit the file, your webserver restarts and reloads everything. In CL, the change is in. Nothing is restarted. The function was compiled and the next call will use it.
Python REPL and CL's are very different!
In CL: you can install new dependencies from the REPL. You can change a class definition, the existing objects will take the changes at the next invocation. You can control how this happens, etc. TLDR; CL is built around live programs. Buuut we can also do it the dumb (and safe) way following the industry's best practices.
re: “”In Python, can you fix the buggy function, re-compile it, and resume execution from the stack you want?””
Yes. I code Python in Emacs with the ancient Python support for running a REPL, loading buffers, editing a function and just hot reloading the modified function, etc.
While this can be useful in extreme situations, such as a production system that can't be taken offline even momentarily, it has always felt like a footgun to me: a way to make critical changes that are recorded nowhere and flow through no change process. Lisps are full of these really powerful features that should be used sparingly, but are held up as reasons to use it. I wrote a fair amount of CL a couple decades ago, but always felt that "pause, inspect, stop, edit, start" was safer than "pause, inspect, edit, restart". Some corollary of Kernighan's Law, there.
You can use the features as little and as safely as you want:
> can be useful in extreme situations
it's already useful in development, it's one of those things that shorten the dev loop and make development in CL a breeze. For production, you choose. Connect to the live image to inspect the state without modifying anything or debug while it's live.
> recorded nowhere
you can connect to a running program and have the source under VCS. The thing to do isn't to copy-paste new function definitions in the live image's REPL, but to connect to the image, make changes to the source files, and re-compile them (a C-c C-c in Slime) (sending changes to the running program).
> safer
yep, some things are safer. Advanced features are useful even for simple things (introspection, etc).
Not the author, but unless the server was running in a short-lived ephemeral container (in which case the management system probably killed the container and started a new one), the CL process would still be around in a paused state, waiting for you to connect to it and tell it how to resume.
https://comp-348.github.io/lisp-debugging.html has an example of what it looks like. A toy example, to be sure, but the basics of a real example would still look the same. You're given a choice of several options, very reminiscent of the "Abort, Retry, Ignore?" choice that used to be oh-so-familiar in the days of DOS. Except this one is more useful, because it offers ways to specify how to resume. E.g., the toy project is halting on a `(print X)` call where the value of X is not defined. And the choices are:
0. Continue. (Retry using X).
In the toy example, this would fail, because nothing else has defined X. But in real code, the name might have been undefined because the data needed to define it hadn't arrived yet, from the database or the filesystem. In which case retrying the statement might work the second time.
1. Use-value. (Use specified value).
This one prompts you to enter a value for the undefined variable, and continues, but it does not modify the value of X in the program. The next time the program tries to use X, it will halt again with another "unbound variable" error.
2. Store-value. (Set specified value and use it).
This one, just like Use-value, will prompt you to enter a value to use... but then it will set X to that value and continue running the program. Next time the program tries to read the value of X, it will have one, and the program won't halt.
3. Abort. (Exit debugger, returning to top level).
This is what you would choose if there's no good way to fix the error, and you just have to quit the program and restart. Though note that choosing this option isn't going to exit the program you're debugging, just take you out of the debugger. You'll still need to kill-and-restart it some other way... or come back an hour later when the value is finally available, and then choose options 1 or 2.
Hopefully that gives you a taste for what the CL debugger is like to use in practice.
This looks like a good debugger, but it's similar to ones I've used in other languages. What's special about this one? I thought the idea was you use the debugger in prod, but now I don't think that's what the article meant.
Yes, you use the debugger in prod. I've read many articles by Common Lisp users talking about how great that feature is: they can quickly get production back up and running, then go to the code and implement the same fix they just did on live prod.
And it's not editing files on the server, it's actually reaching into the running code and tweaking its values.
That, I think, is the difference here. In many languages, the debugger can pause on the exception and let you inspect the code. But in every other language I've used, once you edit the code to fix the bug, you can't resume from where the debugger paused. You have to recompile the code and resume from the top. In CL, you can resume from exactly the state you were in when the debugger paused, only this time with the correct data in place. (Or even with a code fix having been applied, live, to the code).
First , that’s absolutely not how you run a CL server in production. If you had a debugger installed in production, which you shouldn’t, it wouldn just pause there and time out the sockets. What you could do is attach your client to the Lisp system with swank and then make your image changes, later to be synchronized with the actual code in the repo.
Second, other languages can also do that, Java for example has facilities to recompile functions and then rewind the stack to run the function again from the beginning of it. Dart also does that even better and people use that extensively in Flutter with hot reloading. I do that in a debugger during tests though, never seen it done in prod.
Test driven developmet practitioners in Smalltalk used to (still do?) write the test before writing the implementation, run the test, and, when the "method not implemented" error shows up in the debugger, type the implementation and continue the execution. Common Lisp can do that too.
So, in practice, it might look like you hit any kind of runtime failure, and then the LLM writes some code to fix it, and the user's request completes successfully with no errors.
Loved the post, have been batting around similar ideas w/clojure. I have two questions:
1. Have you had much success w/moar macros in the age of the LLM? I've been impressed by the models' ability to write good ones, but I can tell my taste/judgement for macros isn't quite there. But they tend to be pretty good at writing gnarly ones, and I would love to work more macros into my workflow. Would love your thoughts. (Have I taken "Simple Made Easy" too far and left macro value on the table?)
2. Do your models ever get confused with image-based dev, and state? It's seemed dumb to me to have models keep running `sed` to change files, but it is nice to have a human-readable, filesystem-backed record of definitions. Would love to hear your experience here.
I've seen a big improvement in LLMs writing macros since Opus 5.5 came out. What really helps I think is that I've written skill files with my own examples and instructions.
Same thing for image-based dev. without an agent.md file with good instructions on how to work with a live image it will do dumb things. this sort of workflow is jsut so far off the training distribution.
I think what changed recently isn't that LLMs got better at CL, they just got way better at taking my skill/agent.md files and reasoning through them.
I'm convinced we'll be moving at some point to n-modular redundancy systems, using different languages implementation, simultaneously. For the cost of writing code in n languages tends towards zero now (due to the use of LLMs) and the various stacks and implementations shall then cross-check each others.
There shall be one minimal, ultra-hardened, tiny attack surface, "majority gate" picking the answers that most implementation agrees on.
This shall not only detect a great many implementation issues but also it'll help find security issues and platform defects (say the Common Lisp, Haskell, Rust and Python all agree but the Java one fails: in rare case it'll be due to a JVM bug and finding that out shall be simplified).
Code shall be generated from specs in n languages and ran on n stacks. The gate shall return the answer as soon as a quorum is met and, later on, any bogus answer arriving shall be cause for enquiry.
We'll have such systems, it's just a matter of time.
I think what they're saying is that in CL you create DSLs that are tailored for the domain and that will result in lower token use. Not sure about that claim, would like to see some data to backup that assertion. Wouldn't you then need to supply the LLM with a "programming manual" for this new DSL? Wouldn't that cost tokens?
sure, but input tokens are way cheaper than output.
There is also a phenomenon I've named "brevity collapse." Often, when shrinking a codebase, you find you need less glue. Additionally, because it is smaller, you can hold more of it in your head and see more opportunities for shrinkage. Surprising things happen when the bones of your language get more efficient---it's more like going from elephant to flea than elephant to grizzly bear. The smaller scale means there's less "overhead" code, which means you can go smaller still.
Smalltalk, in image based systems, absolutely. Erlang not as much, it's more of a "crash the actor and try again" than "rewind, pause, and handle errors in-system"
Presumably a DSL would allow you to do more heavy lifting in your specific domain with less tokens.
Let's way you wanted to do a web application using LLMs. Using a web framework would cost less tokens than using the vanilla underlying language (Python, PHP, whatever..), which is again way less tokens than building up from assembly (an LLM should be able to do this given enough time and compute).
I hadn't thought of that! Writing a program in fewer tokens* has been Lisp's advantage all along. If, because of token economics, the same advantage accrued at the LLM level, I dare say certain people would be pleased (as would I):
But writing good DSLs requires one to be great at going up the ladder of abstraction. Not that LLMs can't do it, but they stuggle and choose the path of least resistance. That's easier even if it results in more code.
Readability for yourself. I find the biggest issue with lots of LLM assisted development is that the agent harness can generate code faster than I can read it. I'm the bottleneck.
There's a "zen" moment felt by folks who've written lots of macros (experienced in Lisps and Forths) where, when designed properly, you really feel like you've "grown" a language and have really walked up the abstraction ladder. My thesis is that macro heavy code when the author designs the macros well are very readable. That an agent's output when stacked upon macros can be a lot simpler to read and understand than in languages where the syntax is less fungible. And if you leave a project for a while and come back, an agent is a perfect tool to help you read your macros and familiarize yourself with the abstraction surface again.
> I dont really understand what macros get you when llms exist since a llm doesnt really need to create dsls to get work done
Pithiness. A human might need to be able to read and understand the code. If the code is much longer than it could have been, it will take much longer to read and understand it.
I mean its cool but what about all of the extra infra I need to write because support for the lang is not as extensive and so more code I need to maintain?
What to you are the foundational ones? Perhaps CL has them already, or if it's actually a C lib that every other lang uses, CL can use that too. If not, the feedback would at least give people ideas.
From the parent's security standpoint I'm more sympathetic, there's been a lot fewer eyes on CL code, there's no central place to keep track of discovered security issues, and perhaps more vigilance is required against untrusted input compared to other languages. At the same time, every time I've exposed a CL-powered website I've noticed various attempts at e.g. wordpress endpoint discovery and I sleep soundly knowing a wordpress deployment is something I'll never have to worry about or take extra precautions against. Every time I hear about a supply chain attack I also am happy about the choice of CL. (Though in truth it's not to say that such attacks aren't possible, but for various reasons, one of them rather quite embarrassing to the overall ecosystem, pulling a big one off is going to be more difficult.)
> In Common Lisp your program won’t crash, it’ll stop and open a debugger with the whole stack and all the variables. You can just point your LLM at the debugger, and it’ll make its fix and resume the program.
> To my knowledge Common Lisp is the only mainstream language that does all of this.
Sounds like someone who has never used C# and Visual Studio? Even JavaScript is capable of doing this, honestly JavaScript might be the one language with the richest developer tooling of all time (possibly?), sad to say because there's nicer to work with languages out there.
I mean, so can JavaScript then? Why would I want a debugging window to open up for a customer in production? I remember when Windows used to try to open a debugger when certain programs crashed on me as if I knew what the heck any of that was supposed to mean.
Thing is with .NET you can debug a release build, and even do remote debugging... So it's all a weird claim to me. Definitely someone who only uses Common Lisp and maybe a language with a less impressive debugging background wrote this article.
To be fair, I love Lisp, I dont do a lot with it, though I'm mostly a fan of Racket which is the most modern one outside of maybe Clojure.
Yeah, you don't want a lisp debugging window in production. The absence of stack unwinding does enable editing behavior with wrappers. You could have different error handling without changing any code downstream, and I guess an LLM could theoretically change that depending on the bug it's responding to.
But I'm not positive the juice is worth the squeeze, and this article was unconvincing. I mean if you want macros and terse code and a bigger ecosystem, wouldn't Clojure make more sense?
Erm, sounds niche. Like you can normally do all that minus resuming the program in other languages. If this is a webserver, it should be fine to restart it for an update, it's not like I'm going to catch some user's failed request and patch it before it times out.
No debugging window opens up for anybody by default^^ The web server (ex: Hunchentoot) will crash the request's thread and print the backtrace by default. Change a server's variable and print the backtrace in the browser (= dev mode), change another variable and get the interactive debugger (= dev mode++). All this without stopping/restarting the server. Or just restart it if you want.
I think what is referred to here is that an exception doesn't unwind the stack. Even if the exception is uncaught, you can fix the line or value and then resume where the stack was.
No, it's catching/editing/recompiling/continuing at the point of crash, also you can change the current line and variables so many crashes are "resettable" manually.
Visual Studio has done this since the 90s for C++ code (and C# code for a good while also, just most people have debugger exception catching turned off).
How I know? Back in 2000-2001 we were working on a Dreamcast game with a 3dfx/Glide PC renderer, that 3dfx driver on WinNT was unstable and crashed the machine every 3rd-5th start of the game so being able to edit-continue was a damn lifesaver since a lot of common tweaking of custom stuff outside of what connected to the scripting system could be done at runtime.
This is one thing I think so makes people loyal to MSVC despite it having been behind on standards (gonna be interesting to see if they get reflection up to speed for the 2027 release, GCC's there but not yet Clang).
You continue from where the exception lands, but not from the origin of the expection. The .NET runtime for example does termination handling and it's in the spec if I remember correctly.
Common Lisp has a resumption model.
The leave instruction is required to exit a try, catch, or a filter block. At least last time I read the spec (which is a while ago, but still).
I've had some of the same ideas, I would love a full macro system on a dependently typed language. My stab at it earlier in January was not quite the right thing, but I do think this is the way. Weirdly enough concision is even more important in LLM context than in regular human context.
One thing that has surprised me is how good LLMs are writing Lisp macros. All the annoying boilerplate and backtick-comma arithmetic (',' anyone?) that has ground my gears over the years, they cheerfully churn out correctly. I still review it to make sure they haven't done it in a needlessly complicated way, but I wonder if that is just me being superstitious.
I shouldn't have been surprised, because that part of macro writing is purely mechanical syntax transformation—just a "take these tokens and return those tokens" function that one should expect LLMs to be good at. But I was surprised, because for me that was always the hard part.
So now I can think up new macros to abstract over patterns in my code—the part of macro-writing that I enjoy—and then push a magic "implement this" button to make it work.
What I'm not sure of yet is whether this is an evolutionary dead end—a train stop on the way to "you'll never look at code again, so what does it matter what programming language you used to use". Yes, I still look at my code, and this is a pretty nice train stop, wherever the tracks lead to.
You might be interested in Lean. It's my favorite lisp, even though it's really not at all a lisp. It's a nice programming language, and it lets you really hack on commands, macros, syntax, and elaboration.
It's often suggested that Lean is a general programming language but I don't see typical libraries to do basic stuff in it, like an arg parser, web server, GUI, SQL integrations etc.
Can lean generate small static binaries the same way Go/Rust/C/C++ can?
Lean compiles its code to C, so it's pretty good on that end of the spectrum, though it does have a bit of a runtime, so it's certainly not at the very end of spectrum where Rust/C/C++ are (never used Go, so idk). With some optimization my personal experience is that it's not that bad to get it within a factor of 2-5 of half-decent rust speed. My personal mental model of it is like a more-to-my-tastes wildly-faster python but without an ecosystem.
As for library support, yeah it's definitely not a mature ecosystem. But since it compiles to C, the FFI is great, so you can just ask an LLM to write bindings to your favorite rust/C/C++ library and they do just fine. I recently wanted (apache) arrow bindings for lean, and LLMs knocked out perfectly sufficient bindings just fine.
Common Lisp isn't even a good programming language. There's nothing in CL that isn't in a ton of other languages now. When it was being designed it was way ahead, but now it's just way behind.
Maybe you could take CL as a foundation, introduce modern features and uniformity to the language, remove some of the insane complexity, tame the unhygienic macros, and come up with a pretty good language. Since about 7392 different flavors of scheme have tried to do this and mostly failed, I think this is very unlikely.
Lisp isn't the best language for every problem; it's a language for making better languages for particular problems.
So Lisp is better than itself.
If the extension improves something without making anything else worse under the chosen criteria, that's a Pareto improvement. Otherwise, it's a tradeoff.
That was my assumption too (not that long ago, laggard that I am), so I expected LLMs to be less good at Common Lisp than, say, Javascript or Java, and surely to suck at Arc (the Lisp that HN is written in). But it's not so. They excel at Common Lisp—which, ok, has decades of history and documentation which the models have all slurped up. But they're even good at Arc, which is pretty far down the long tail. And when they mess up, I put something in agents.md and that issue mostly just goes away.
Managed vs unmanaged is a completely different level of performance.
CL is very competitive with popular managed languages like C#, Go, Java, etc despite those all getting tens to hundreds of millions in investment while SBCL gets basically nothing (imagine what it could do with serious investment).
In my experience, LLMs instantly find the reason for the crash and fix. They don't even need to go through the debugging phase anymore. It doesn't matter if you're generating c++ lisp or cobol.
While the open source library ecosystem for CL pales in comparison to Python's or JS's or Java's (or even Julia's, if you talk about certain domain specific stuff), it's not like there's nothing. It really depends on what you're doing, but many sorts of common tasks have a library to help. https://github.com/CodyReichert/awesome-cl
Plus, it's straightforward to make use of libraries with a C API, there are a few slower ways to call out to Python libraries, and there's a whole CL implementation built on the JVM if you really need Java libraries...
I think half the article talks about why CL is good because it can resume from an exception, and half the article talks about why DSLs are good.
I think others have pointed out that most modern scripting languages can halt at exceptions without unwinding the stack. Python & Node both support this with core tooling.
As for DSLs, they constrain the LLM which generally helps with code quality. However why implement your DSL in the unconstrained chaos of CL? You can write DSLs in Rust which gives you static typing, a borrow checker, and clippy.
Common Lisp has these capabilities _without_ dev tooling, via its:
- Conditions[0]
- Evaluation and Compilation[1]
I use both extensively while developing, debugging and analysing. With SLIME (this is a standard and very very slimmed out development aid) you add a debugger hook that allows restart, return from, move down, move up and etc into your available RESTARTs.
What’s missing in most languages is truly native hot reload. Python, for all its dynamicism and being lisp for dictionaries, surprisingly can’t really do it. Maybe node can, but as far as I can tell typescript can’t and I’m not letting the shoggoths touch plain js.
I've never understood this. You can't just combine the state of an old program with new code, without lots of weird things potentially happening. The algorithms and all intermediate products would have to match, at every step of the programs.
That’s why working with lisp feels like touching alien tech - it works, it’s designed for that exact workflow.
(It’s also essentially pointless when your unit of deployment is a disposable container and not a long running lisp system, but it at least makes development a bit nicer)
Weird thing will happen in Lisp just the same when the program state doesn’t have the shape the changed program expects anymore, or vice versa.
This is like claiming that you don’t have type errors in Lisp because everything is a list in Lisp. In reality type errors will either manifest in different ways, or still explicitly occur [0].
Almost nobody programs in functional style in Common Lisp, even though it’s possible. In the vast majority of CL programs there’s tons of global state and yet one learns quickly how to architect and structure the program in order to make image based development and runtime code modification a breeze.
So no, the magic sauce is not “functional”, it’s that CL stemming from the Lisp Machine paradigm is designed for that sort of experience whereas something like Python very much isn’t. And it shows (there’s no image based development in Python that doesn’t end up creating more problems than it solves but image based development is not only the norm in CL but also tremendously empowering).
Scratch is probably the most used programming language with hot reloading, allowed for by its persistent global state.
Web development supports hot reloading modules, but this is effectively just dynamic imports and relies on the app being written for a library/framework that encourages pure code; it doesn’t kill the old module.
I keep bouncing the idea around of building a core in C or Rust then writing everything atop it using Janet. I love how readable lisps are (with macros, you can really move up the abstraction ladder quickly) and working on a live image should make iteration a breeze in an LLM.
I built a OCaml core with a webserver - then made my scripting language on top a custom scheme. I love your idea! I think you would find that a solid interpreted language living in a webserver is an extremely useful think to have. Let me know if you build it!
What did you write with your webserver? Hosting a personal blog? I'm thinking of a good usecase for this. I'm thinking about a game of some sort but not sure yet.
I've just been dogfooding whatever I can to build it out.
Webpages, zettelkasten, todo, workout, diet tracker. It's a web framework! Creating an endpoint is just like making a function in emacs.
Since lisp is homoiconic the AST is just raw JSON. I save the AST in JSON stores in Postgres. But you can clone the AST down and then eval against a local copy of the REPl. So you get local eval for free.
Of course you have to rebuild git to manage a branching REPL in this way.
That works great. The compiler language doesn't matter that much but your language needs a 'runtime' and that is much less annoying to write in C (possibly with bits of assembly, depending on what you're doing) than almost anything else. To a good approximation, your 'compiler' or 'interpreter' amounts to mapping your real language onto a sequence of function calls backed by the 'runtime'.
Lisp allows for some really, really complex and spaghetti code bases. I have seen the ultimate macro-hell from top to bottom. I know syntax does not matter, but i just cant honestly say i read lisp code as clear as something like Go. I guess it boils down to style. I have seen 100 lisp styles, and only one Go style.
Lisp is a sharp tool and I think it struggles to scale with large organisations. You would need incredible tech leadership and a very considered org structure and architecture for it to work.
Culturally, I feel a place like Valve could make it work but you could turn around and ask why does an org need to be in service to and arrange itself around a tool.
I recently wrote a comment on another thread that I think fits even better here:
In my circles I've noticed it's very easy for us to rationalize why our previous favorite language is also the perfect language for the agent era.
If your favorite language before was Python, why, LLMs are fluent in it! So much training data! So many libraries! Home of machine learning! None of that pesky compile time, agents don't need compile time safety anyway, they write such good test coverage! It's The Perfect Agentic Coding Language.
If it was Rust, by jove, an agent can easily handle the headache of satisfying the borrow checker, and now you get the best of all worlds! Safety! Near-C runtime performance! Abstractions! The only reason people didn't use Rust before was it was Too Hard and there were Too Many Furries and now it's not hard and you don't have to interact with them, so get on board. It's The Perfect Agentic Coding Language.
If it was Golang, oh my goodness, what a choice. Pretty fast compile time and pretty fast runtime. Agents get a tight feedback loop with build->run->test->edit. Not very complicated, code has to be written in a straightforward banging-rocks-together way. Good stable ecosystem! Rob Pike designed the language for people he said were "not capable of understanding a brilliant language but we want to use them to build good software." That's an arrogant, demeaning way to describe your colleagues but if they're LLM agents it's dead on! It's The Perfect Agentic Coding Language.
I could go on and on. I'm not immune either! My own favorite language is F# and when I feel like self-justifying, I play the same game:
It has access to the .NET ecosystem like C#, but I don't have to constantly remind the agents to prefer a style with immutable data and pure functions, they idiomatically do that in F#. Files have to be in order and can only refer to symbols defined "earlier" in order, if you want mutually-referential types or functions they have to be declared as such in a joint statement, so spaghetti is hard to create: each project's codebase naturally ends up in a layered bottom-to-top architecture. The language is terse enough to be token efficient, without being symbol soup. FSX scripts can be generated during agentic code reviews to demonstrate repros for discovered issues. If there's any type of code that still warrants me jumping in and writing some myself, that code would be data type definitions/domain modelling, and F# is a joy to write those in. It's The Perfect Agentic Coding Language.
Spot on! People do really enjoy NOT having to change. We can come up with many excuses why we do not have to do anything different. I'm finding multiple rrasons why I should stick to laravel.
Could be said that it was the path of least resistance, but the funny thing is that most of that resistance is you just being in your own way.
The truth is that it really doesn't matter that much.
Use the language that you like (enjoy your favorites!) and that binds well with other tools you're using. I'm currently working on a project that's all C++ (wxWidgets frontend, plus C++ backend). I've used LLM tools with it, no issues. Would be the same with any other language, from what I can tell. I have another project in Go I'll probably try it on at some point soon.
But no seriously F# might actually be the perfect agentic coding language. I had the good fortune of learning OCaml a few years ago as the models were getting good. Had a blast.
I think Lisp and an ML are kind of the dynamic (static) duo. I still think MLs are the best for complex projects, but interpreted languages are pretty neat too. Python, Rust, and Golang are all great, also.
I think a tree calculus language might be the actual best - but it's too soon to say
So now we're believing it's great that LLM's can write macros, thus further obfuscating human maintenance of the code-base, because LLM's can understand and implement it than better than humans.
This isn't the future I wanted, and is why I advocate for trade schools these days.
I miss the days I could listen to music and use my adhd/autism to its fullest potential reading documentation and figuring things out myself.
Of course, these are same people you'd want managing these AI agents in the first place, but reviewing code written by others always kind of sucks, especially when it's assumed the language model knows better than you do which isn't often the case!
Was the PRD perfected on the requirements? Only God knows, and I personally want to be there when it's written.
I must be proving your point because I'm a rust head and the argument you gave for rust just seems like the best one haha. And if I had the choice between two vibe-coded pieces of software, and one was written in rust and the other was written in python, I would far prefer to use the rust one.
Why, you are right on all of these. I used Rust a lot before AI, and I use it a lot after. But I recognise the shortcoming of long compilation: it doesn't just shorten how many times I can have feedback in one day, it is also difficult to fit compilation into constrained environments. Like when an ultra secure Kubernetes cluster doesn't let me compile source code anywhere near it, and I have to compile inside of it; my colleagues insistence on using Go here because "Rust is difficult" (it surely is 100% longer to type when prompting an LLM) really has the main benefit that this code compiles fast and easily within the cluster.
So picking languages for their compilation and especially runtime properties is key.
What I've learned, though, is that languages with reckless error handling produce more errors at runtime. My Rust programs just don't crash because I don't need it to be explicit about reaching a "total" approach.
If you're in a C# environment, I see a case for F#. And if you want fast but more solid than Python, why not Mojo? Although who reads code.
This article convinces me. But I suspect like my lack of domain knowledge of Lisp makes me spend time learning the runtime: how and when can I switch to JIT, what's the async story, how does the harness become part of the Lisp program, etc.
Yeah... no. I don't think there is a best programming language, but I do think dynamically typed languages are worse for AI to code in compared to statically typed ones.
This post reads like it was written by a Common Lisp fan who is looking for a reasons to say that Common Lisp is good for AI agents, rather than an AI agent user evaluating what languages are really best.
I've heard fans of both statically typed and dynamically typed languages advocate for their language. The static type fans say that the rigorous compilation process gives the LLM a fast iteration loop with clear messages from the compiler on what invariants aren't being upheld. The dynamically typed languages advocates talk about fewer tokens, the popularity of the language in the training data, and so forth. Guess what, these are the exact same arguments these communities made for human developers.
Personally, I don't know the answer. The industry has swung back and forth on this over the decades. Before AI code-gen static languages were on the upswing for a variety of reasons, including runtime efficiency and much better ergonomics thanks to modern type inference engines. I suspect those reasons are still valid, and also that statically typed languages give LLMs a leg up because it is easier to reason about them locally thanks to declared types and information hiding.
It seems like an assumption here is that any language will be equally performant at runtime, for a given number of tokens burned to produce the code. I have never used any functional language for anything real (except maybe Mathematica). Is that true?
Common Lisp is not a functional language. You can write functional-paradigm code in CL but it will happily let you write old-school imperative style, OOP, etc.
The de facto open source implementation, SBCL, has a solid compiler and garbage collector. It produces fast code.
SBCL can produce very fast code. It's really slept on from that point of view.
A couple of years back there was a paper doing the rounds about software execution efficiency, which a lot of people in my neck of the woods got interested by because of the potential for environmental impact at the sort of scale we operate at. SBCL ranked disturbingly highly for something that is culturally never going to happen for us.
Bad performance is much less excusable these days (https://danluu.com/perf-opt/) and usually it's more the result of architecture and data structure choice than presence/absence of things out of the box of some language (though in the case of C++, it seems that everyone who gets obsessed with performance just says to avoid the included stdlib no matter the compiler; so much for that box). Your question seems to me to betray an assumption that Common Lisp is particularly functional (and overall suffers from some sort of functional programming performance trade-offs), when it's not. Common Lisp is unopinionated, multi-paradigm, but at its core is mutation-friendly, compilation friendly (compile is a function available to call at runtime), and object-oriented (CLOS being the first ANSI standardized OOP system, it's more flexible than most OO systems and methods do not belong to classes). There are also many implementations of Common Lisp, including two long-standing commercial ones, so should performance (whether raw throughput, or memory size, or whatever your metrics) not quite be to your liking out of the box with one, you might consider another before giving up and choosing an entirely different language or spending time/tokens trying to better optimize your existing code. That said, SBCL is probably the most popular, and has a pretty sophisticated optimizing compiler that produces pretty fast native code by default, while allowing you to specify your own assembly ops without actually having to write separate assembly files. (You can specify the assembly with more Lisp code.) See for instance https://www.stylewarning.com/posts/nbody/
For LLMs, less code means fewer tokens, and tokens are what you pay for so you spend less on development.
I don't think that's strictly true.
What you need is for your LLM to be able to understand enough context to be able to make a change with as few token as possible, so if your code isn't expressive enough or if it has a tendrils calling lots of different functions/methods all over the place, then you'll have to give it much more code (context) than if you've got nice encapsulated modules that don't depend on other parts.
The design of your architecture (probably?) has a greater impact on token use in a large app than the language it's written in. Although, obviously, languages lend themselves to particular architectures so it's correlated.
I remember the old discussion between clojure programmers and someone who works in a strongly typed language. The argument was “types allow you to be certain that if you change some code, an unrelated but affected part of the codebase will also be flagged and it is not missed” the clojure guy response was “why would you ever design a program where a change in one place affects the other”.
I do think this is an unrelated win of functional languages that hasn’t yet been “discovered” by the vibe coder crowd - FP’s whole premise was that it makes your code depend on much much less things so you can “fit it in your head and reason about”… that’s like the perfect sweet spot for agentic as well, we just haven’t seen tools utilize that in earnest.
Exactly. It's painfully easy to break duck typed functional code in confusing ways by subtly changing the semantics of a higher order library function that's used all over the codebase. I love lisps but IMO in general not having static types is on par with not having a debugger. It's probably my only real complaint about standard scheme.
> by subtly changing the semantics of a higher order library function
Why would you do that? Take common lisps, most of the functions has been standardized for ages. It’s like saying by slightly adjusting the rules of addition, you can break maths.
Something that is going to be used all over the codebase is basically axiomatic and needs to be written and modified carefully.
> Are you really asking why someone would find themselves needing to tweak the edge case semantics of a widely used function in a large project?
If you do this and breaks the assumptions of the interface, it’s just recklessness. You don’t go and tweak functions without fully understanding the rules it established and how those rules are treated as axioms somewhere else.
> But notably compile time static type checking greatly reduces the risks involved
This is usually an excuse to not fully check the assumptions in a given piece of code. Like tbe warnings will be enough to take care of any issues. Type checking is not software correctness. I’ve seen badly defined type, type erasures, and various other issues that negated any value brought by the type system.
I got downvoted to oblivion on a reddit thread where someone was asking how they could handle the massive context that an agent needed to load to understand their codebase when I suggested that exactly the same techniques which let humans manage it would work on an LLM.
Models aren't good at this by default, so you end up with spaghetti mess. But if you tell them that you want strong interfaces, isolation, and types, they can work that way.
>What you need is for your LLM to be able to understand enough context to be able to make a change with as few token as possible, so if your code isn't expressive enough
Recently there's an article on language plasticity in the era of AI/LLM and why D language is very well suited for this era [1].
Perhaps we need a proper benchmark similar to Beaver but for AI assisted coding for different programming languages instead of Text-to-SQL [2].
[1] Language Plasticity is More Important Than Ever:
Additionally, if there’s more “knowledge” of the code and best practices and such baked into your model, it has to spend fewer tokens reasoning about it or even potentially looking it up. I think even one web search to retrieve additional information not also in the model will probably blow up any supposed benefit you’d get by a slightly more token-efficient language.
That could be mitigated by using a much smaller non-reasoning model for searches and reading. There's no reason why a search should take loads of tokens.
This is one of the most inane, fluff articles I’ve seen dumped in this site in a while. Nothing original, just a simplistic regurgitation of known stuff. Reads like lame, lazy LLM slop :(
An ERP system in...LISP? None of the reasons given as to why that would be a good idea are really very convincing or specific to LISP?
The main things you want for ERP are just to simplify database interactions as far as possible and to give you as many and as customizable options as possible for data visualization and curating information for a non techie user. I dont really get why LISP?
I don't really agree with this. Having strong types and guardrails like in Haskell, Rust or OCaml helps _massively_ with LLMs because they get very clear messages back, and can express their logic using semantic types they can easily follow. Conversely, I've noticed that AIs (just like humans) tend to kind of lose the thread with very dynamic code
I've found the same: strong types are disproportionately valuable in LLM coding vs human coding because they essentially function as context and the typechecker actively enforces correctness
> strong types are disproportionately valuable in LLM coding vs human coding because they essentially function as context and the typechecker actively enforces correctness
One could argue that this was a large benefit for human coding long before LLMs became useful.
It is why I always preferred strongly-typed languages.
And once we had practical strongly-typed languages with implicit type inference I really couldn't wrap my head around why anyone would prefer dynamic typing other than just inertia due to that being what they were used to.
I guess people who are against static typing are not working on aviation type projects - more like worst thing that can happen on what they work is a div slightly off.
I'm definitely going to steal double-entry bookkeeping from you. Someone will invariably dismiss aviation safety analogies as histrionics, but everyone can get "you want your money correct, right?".
> Someone will invariably dismiss aviation safety analogies as histrionics
I encounter that regularly, as I often use aviation analogies to guide me in programming. D's design has been significantly influenced by my aerospace experiences.
I wouldn't say it's disproportionate. Strong static typing is massively valuable for humans too. Maybe it's just more obvious to some people because you actually can write the same large program twice using the same "person" and a different language.
Previously studies into the benefits of static typing for humans were always a bit flawed because you can't do that with people. And tbh I don't know why but there are a surprising number of people that don't appreciate static typing. My guess is a combination of ego and laziness, which doesn't apply to LLMs.
Yes! Maybe the current limitation is due to their limited context and frequent compaction. Where they can lose track of (abc-xyz A B) and what A and B need to be.
I’m not sure if the Common Lisp compiler can be very helpful either, since it’s a dynamic language and A and B could be many different types (duck typing).
CL is strongly typed. It is quite refreshing to not have to worry about implicit type conversion (or worse) all over the place, unlike some other languages...
Type declarations are also optional and compilers can create compile-time warnings about them. Thus for many trivial cases, when using SBCL some obviously wrong types, or typos, or miscounted arguments, can be caught ahead of time without having to execute code. CL is also not duck typed. If abc-xyz is a generic function, selecting which method to call relies on the actual class hierarchies of the given A and B objects, there's no "duck shape" shenanigans.
For static types, well, CL is flexible enough to bolt such a system on top as a library, where you'll have a full ML/Haskell style type system. https://coalton-lang.github.io/ But it seems the relevance for LLMs is rather mixed, much like studies from the last few decades on static/dynamic typing in general: https://danluu.com/pl-tokens/
It's possible to write the whole system only by defining the types.
The "glue" can be sloppy but as long as it keeps on the edges the output is most often fine.
Recently I'm on the fence about Rust vs OCaml (but plan to write about it soon) because I have ~700k LoC in Rust but my workflow starts to get seriously dragged down by compilation/tests in isolated worktrees.
I recently also dab with Gluon (as embeddable type safe scripting) and rule-based-development for maximum code control/agents output leverage.
OCaml core + interpreted Lisp on top really feels like total enlightment, I have to say.
The dynamic features you get with a full blown REPL are, in some specific cases, worth the trade off you get by losing the guardrails (which I call Rubber Baby Buggy Bumpers).
You're right though - strong typing feels like a cheat code.
Absolutely this. Plus, LLMs are surprisingly adept (in my experience) at handling gnarly syntactic corners of most widespread strongly typed languages, esp. C++ and Rust.
A lot of people confuse strong typing for static typing. Dynamic with untyped, too. The situation hasn't improved at all in the last decade. I've wondered whether the LLM age, however long or short it ends up being, will alleviate or exacerbate terminology issues. Potential sadness and excitement either way.
And I think this point is a poor as any made in the article.
Last I attempted to write some smaller ocaml project I used LLMs for support (but wrote myself). They generally weren’t excellent.
Honestly, types don’t help as much as people want them to. The LLM does best on popular languages, especially those whose use and feel is also mainstream.
That is to say, pick a niche language like Odin, and it may incorrectly start to over-apply Go’isms - knowledge from one language bleeds into how it approaches others.
What the heck this thread is about, Common Lisp has types and you can use them in ways most strongly typed languages wouldn’t let you! They are basically expressions, which gives a lot of flexibility. It may not be always checked statically, but in practice SBCL does a great job at doing it or at least emitting warnings when it can’t prove the code will fail at runtime.
Also if you really want Haskell types you can use Coalton, which is essentially Common Lisp with Haskell types, but lets you interop with Common Lisp seamlessly as Kotlin with Java.
Are you saying this based on the fact that no-one will read or understand the code manually? That might be the case but on some areas, having a product understanding and knowing what to build, why to build matters a lot and sometimes the LLMs will write code that is not correct, doesn't make sense and they will continue down that path without explaining it.
I still believe that other languages which are understood by developers are and will be required and LLMs are trained on the same dataset so it can write the code.
"Would you agree that some programming languages are better than others? If so then one of them must be the best."
Unless of course there are multiple dimensions of "Good". I in my opinion this is exactly the case. There are best languages per dimension, but not absolutely best.
Yes! It depends. That's why there are so many languages that still exist. Otherwise it would be a winner-takes-all situation and there would be just one left standing.
Ah, but you forget the attention economy that the internet amplified: only controversial takes are discussed, nobody wants to read your reasonable and nuanced blog post because everybody can silently agree with it already. Without any crucial insight or big controversy nobody will read it. And the latter likes to masquerade as the former most of the time.
Even if there's one dimension, this is not a logically correct statement. Some numbers are bigger than others, but there is no biggest number. Or it could be a partial ordering - say that a number x is "better" than y if y divides x. Then out of the first hundred numbers, some are better than others but there's no single "best".
“a partial order on a set is an arrangement such that, for certain pairs of elements, one precedes the other. The word partial is used to indicate that not every pair of elements needs to be comparable; that is, there may be pairs for which neither element precedes the other”
Zero visible experience with CL on top of a project list where half of the links give a 404.
Others are unmaintained slop output.
Sprinkled with Paul Graham references and the Harvard badge.
Sorry, I'm not impressed.
How did that /run/ to the top of HN? [pun intended]
Lisp and functional languages have a determined following. I rather like both too, but if a claim like this was made about any popular language, there would be a lot more detractors in the comments. I was hoping this would be an interesting article with actually good points so I could use them as incentive to finally get started on making some with Common Lisp, but it was very poorly written.
I have similar experiences in Clojure. I use Pi and created an extension which teaches the LLM how to start a JVM with a Clojure nREPL listening in it. Added a tool which lets the agent evaluate any Clojure form inside the JVM. Sprinkled the setup with clj-reload and now the agent is using the nREPL as if it were its second nature.
What I like most is how fast testing goes: it modifies test code on disk, asks clj-reload to reload all affected namespaces, then reruns the tests. It's also fun to see how it verifies its assumptions by evaluating short programs through the nREPL.
I asked it to organize the various subsystems inside my playground repo into Integrant systems and make it possible for me to say things like "restart the http subsystem".
It also understands shadow-cljs: I replicated the necessary parts of the shadow CLI tooling in Clojure; now I can compile, watch and serve any number of CLJS apps located in various namespaces from inside the same JVM. There is no need to touch the command line any more: I just instruct the agent to start a particular CLJS app and it's there.
Note that this may not work for you standalone as it relies on my other agent-sandbox extension (which ensures all agent operations happen inside a sandbox container).
But point an LLM to its source code and it will extract the gist of it.
Totally possible in Python, but clojure is typically more amenable to this kind of change because of how data is immutable by default, you can just replace without any consequences because the application is already developed with that concept in mind.
For iPython that's totally possible because the model there is just some kind of dependency graph. Regular python programs, much more complex I'd bet unless the architecture takes it into account
> For iPython that's totally possible because the model there is just some kind of dependency graph. Regular python programs, much more complex I'd bet unless the architecture takes it into account
The iPython shell has %autoreload 2 (autoreload on changes) which is incredibly helpful for debugging and iteration.
I do agree that Lisp is better, hooking an agent up to Emacs is a lot of fun (and I'm only getting started!).
Seconded. I get excellent results with LLMs and Clojure. The first large LLMs struggled with less popular languages, modern LLMs don't seem to have this problem. I get excellent code quality in a large codebase.
Similar experience with Clojure here. IMO the functional orientation of the language makes it so that the LLMs don't write as much bloat, and it's much more pleasant for me to read and hand-refactor than TypeScript
I don't let the LLM write macros though, it creates too much opacity for me to easily reason about what they've done.
Seems to be written by somebody who has never worked within a team, never published a commercial piece of software (with the said team), and never used an LLM to write code. How is that possible? I love Common List and Racket. Love them to bits. I'd use them all day every day if it weren’t for that pesky LISP curse and the fact that nobody would want to work with me. LLMs, until recently were consistently forgetting parens, couldn't close functions properly. Somehow LLMs can't really count in their head, so it makes for a really... fun time with LISPs.
- has opinions about one way of formatting and codestyle
- has opinions about linting and integrated debugging
- has predictable strong types
- has opinions about integrated unit testing and a standard way to write them
- has an integrated toolchain
- has a strong stdlib and an upstreamed way to unify libraries
- (recommended) has a standard project layout _where_ to put its code (types, structs, helpers, utils, etc)
Go and Rust fit all these checkmarks except the last one. That's why those languages don't need kilometer long prompts to tell the LLM how to write code. Most of the prompting in those languages focuses around architecture and design, and not about style, tooling, or other artificially vague decisions.
Lisp is the most unopinionated language there is, therefore it is the worst in terms of lack of decisions encoded in its tooling.
And I'm not writing that as an opponent of the language, I've written scheme bindings for a couple of years in (academic) robotics.
The point that I am making here is that you _need_ an opinionated language for an LLM to make sense. Write linters and tools before code [1].
Modern LLMs have absolutely no issue with writing reasonably good Common Lisp without a kilometer of prompts. It's true that you did need this a year or two ago, but not anymore. In fact, LLMs can even write Coalton pretty well if you prefer strong, static typed all the way through.
Lisp isn't some language wild west lacking style and idioms—regardless of whether there's a "go fmt" for it or not. Most open source Lisp code is quite standard and "boring": functions and classes/structs following canonical indentation.
Soon, hopefully, we will have evidence based analysis of those programming languages best suited for LLM authoring. Fwiw I doubt that CL will make the cut - but happy to be proven wrong. Obviously a large factor remains the facility of the human driver for a particular language.
Until then, I subscribe to the view that strong types fit LLM coding well since LLMs are prone to make silly mistakes when they patch together code examples in dynamic languages.
[Disclosure, my new language https://bil-lang.org aims to add “strong typing” around parallel programming to help weed out deadlock conditions.]
I feel like everyone has some sort of justification for why their pet language is now the best because LLMs can work in it better for a bunch of reasons.
Javascript/Python is the best because there's so much code out there that the LLMs can train on, and LLMs can write lots of tests to make sure everything is correct. There's nothing to compile, so the LLM can iterate quickly.
Rust is the best because the LLM gets great feedback from the compiler because of its strong type system, and it can deal with the borrow checker for you.
Go is the best because it's a simpler language with a decent type system, and the LLM can reason about that well, and will never forget to check an `err` return. The compiler is fast, so the LLM can iterate quickly.
I could write similar praise for C, C++, Java...
At this point I don't think any language is the best "because LLMs". I think there are quite a few languages that LLMs are probably not good at, but you have lots of choices if you want something they are good at.
I have a pet language Inm love because LLMs have shown themselves to be quite lousy at writing it. It makes programming a lot more fun when you have to do it yourself.
I mean, it can still reason about it, but the code it (Claude and Gemini) frequently doesn't compile and it throws edits onto it until it does. When it then compiles this pretty much c# written in a functional HM-typed sexpr language that lacks classes.
AI has been great help in writing the compiler, though. I got stuck in codegen after having written a lexer, parser and type checker, and not only did it make a faster and better code generator than I ever could, it also made the type checker about 5x fater.
I've been pondering this too. Just making a dialect of a language so different the llm's would fail. And then never release any descriptions or examples of it so there won't be any data to steal.
As the models get better we're increasingly into splitting hairs territory with these comparisons. A model might be more effective at Common Lisp than at Turbo Pascal but we're talking perhaps few tens of percent rather than some orders of magnitude differences. It doesn't really matter all that much any longer.
You forgot: Objective-Smalltalk is the best, because Agents need architectural guidance to avoid turning your codebase to mush and Objective-Smalltalk lets you express the architecture (and its constraints) directly in the code.
It also allows for coding at a higher level, meaning that code is closer to the prompts.
Is it actually true? OF COURSE IT IS!!!
So no idea, but after long dismissing that idea precisely because it looks like a "just so" explanation for my obvious favorite choice I am starting to come around to the idea that it might just be true in spite of the obvious bias...and definitely worth testing.
Go is the best because it has a pretty fast runtime, very fast compile time, rich ecosystem, and is simple to deploy.
Fight me. As a classical software engineer, I hate Go, but in this new era it wins so easily. For web, at least. The subjective stuff about how the language feels is all out the window now.
C was 1972, and golang was 2007. It's not that no new concepts <cough>exceptions</cough> were discovered in between.
A pen is more elegant than a chisel.
All those things are true of Java/Kotlin, except even moreso. So LLMs should all write Java.
There's no good way to resolve these kinds of debates. I don't think AI changes much. It thinks in the same sorts of ways we do, just faster, so things humans find hard or unproductive can also be hard and unproductive for AI too. There are a few exceptions where it's able to reason fast enough that things which would be dumb for humans (like reading raw assembly or bytecode) are no big deal for AI. But mostly it's the same.
> All those things are true of Java/Kotlin, except even moreso.
The parent did mention:
> ...and is simple to deploy.
Also, my experience with Java programs is that the memory overhead is even higher than Go (where it's a ~2x of what the program holds as heap due to the GOGC=100 default).
It's not harder to deploy in my experience. When people say this they tend to imagine deployment as "scp binary user@host". Well,
./gradlew installDist # build the app for deployment
rsync -avz --delete build/install/my-app/ user@host:my-app/
Just repeat to upload new versions. rsync vs scp isn't harder and the Java version will be faster (incremental). If the host doesn't have a JVM installed, ok... apt-get install one and your distro will keep it up to date. One command.
But in reality most software isn't deployed by copying binaries around. You'd want it to be at minimum run by systemd or kubernetes, for example. And if you want a Docker container then it's pretty easy. Your framework probably configures it out of the box:
./gradlew dockerBuild
Push to the host and start it up.
The reason it's not harder in the end is that the above takes cares of many annoying details that crop up in real deployment, like knowing what CPU and CPU extensions does the host have? Can you deploy incrementally without recopying the whole thing or does that not matter?
The above is for servers, but it's not really harder for CLI tools either. Fat JARs exist. If the user doesn't have a JVM, once again, they can install one easily from their package manager and then it's done - no need to create and distribute half a dozen binaries for all the different OS and CPU combinations that are out there.
But if you want to make AOT compiled binaries and get lower memory usage too, there is GraalVM which can do both. You've got the choice.
Note how the above debate isn't changed by AI in any way. Their weaknesses remain weaknesses, their strengths remain strengths. I wouldn't personally use Go because of its poor feature set and debugging support (errors don't reliably create stack traces). But the arrival of LLMs changes nothing about these preferences and choices. At most you can talk about token efficiency, in theory, but the attempts to measure the real world impact of that don't seem to have yielded decisive evidence.
> And compared to Go, the production observability is very good.
Can you give some examples where Go is lacking and Java is great? (Ideally builtin things, and if not builtin, then things where Go doesn't have an externally developed alternative).
> I also use Clojure and can observe my running app with a full REPL.
Does this work with other JVM languages, like Java, too?
> There's no good way to resolve these kinds of debates
This debate in particular is easy to resolve: there is no “best“ language for all use cases and I agree LLMs have not changed that. The best language for a scenario depends entirely on the scenario, so talking about best languages without a scenario in mind is completely the wrong discussion.
Absolutely agree. "Best"? Best for what? For writing programs? But what kind of programs? "General programming".
I've never written a general program in my life. I've written a bunch of specific ones, though. What I care about is which language is best for writing this specific program. Why do I care about which language is best at writing a program that I'm not trying to write?
No. I’m sick of flame wars, and I already was before LLMs turned everyone even more insufferable. You can fight yourself in your own corner, if you like. Never thought I’d miss emacs VS vim.
Use whatever language you want, I couldn’t give less of a shit. I have no desire to waste time on a dick-measuring competition, and that’s doubly true because people in these fights are measuring other people’s dicks.
> As a classical software engineer, (…) The subjective stuff about how the language feels is all out the window now.
Subjective stuff never mattered to people who take no pride in doing proper work. That hasn’t changed because of LLMs, it only shone a brighter light on those people.
But it hasn’t changed. That’s the whole point of the comment. We’re still at the same flamewars and (lack of) care as before. We’re just doing more of it at a larger scale.
I do agree the comment came off strong and for that I apologise to the person above. I meant to make a general criticism, not condemn any particular individual.
Java has an extremely fast runtime and rich ecosystem. If we're going to never look at code and let AI handle it all (not sure I agree, but let's assume), then Java is the obvious best choice.
Java has a slow iteration time - long compilation and slow startup. Common frameworks add even more time before the program does anything useful. A fairly complex Go web backend can recompile and completely start in under a second. A relatively simple Java backed can take more than ten seconds.
Only if you holding it wrong, write the same stuff in Java like in Go, without frameworks, all by hand and you will get your second.
Even better don't throw away the frameworks, learn to use JIT caches and hot code reloading tools, and you can even debug, edit and continue from the comfort of an IDE, doing several useful actions.
I've written plenty of C++ for embedded systems, and very little Go, but if I find anyone trying to connect C++ to the web I will try to get them fired for cybersecurity violations.
Go is close enough on runtime for all reasonable purposes.
If you statically link C/C++, you lose access to vDSO by default. Syscalls like clock_gettime incur normal syscall overhead. You can still retrieve vDSO at runtime like Go does, but that's something you have to do yourself in C/C++.
AI + smaller docker images made me look at it again.
The fact I can test/build concurrently and much faster than rust, off the same repo without messing with sccache and worktrees, make it easily the winner for many web/self-contained scenarios.
I'm making a galaxy civilization simulation using go-lang and it is sooo fast to compile and run, even on many iterations of the simulation, and I have 0 dependencies on risky 3rd party libraries etc.
As a quite well seasoned software engineer working many years with Java, C#, PHP etc. both in complex calculation systems and webservices, go-lang just feels superior in so many aspects. While I might still fare better in say Java is purely a function of me spending much more time in it.
So I see your point in myself and agree and agree. (I just don't hate go, I think we need to give it a chance)
I can imagine that for web dev. But as someone doing numerical physics simulations, would you really recommend Go over either C/C++/Fortran for low-level, Python for high-level, or Julia I guess for both categories?
Unless you are doing very low level C++ manual memory optimization, I would recommend using Go for both: it's on the same scale of magnitude of speed as C/C++/Fortran but the compile time is crazy fast that prototyping in a high-level scripting language like Python really isn't that necessary.
You also get very easy parallelism in Goroutines, excellent ecosystem, and one of the best performance profilers in any programming language, pprof.
Interesting. Parallelism and profiling are both strong selling points.
I’ve done pretty low-level C++ and Fortran in the past, but these days I’m honestly mostly used Python with either NumPy or CuPy for calculations, so not very low-level at all. My code is pretty performance-sensitive though, so unless the main operations can be framed neatly in terms of NumPy or CuPy primitives, one had to drop down to low level.
Do you know how the Go library support is for typical scientific computing stuff, e.g. matrix diagonalization, sparse matrices, or handing off such calculations to CUDA (or other GPU frameworks)? Is there a strong numerics community in Go these days, or would you have to implement most of what you need yourself?
For sparse, I think the best is still Intel's sparse libraries for MKL on CPU, it's just not a workload that runs well on GPU compared to dense SGEMM. You can call it via CGo.
For GPU offloading, there should be a decent amount of CUDA bindings available for Go right now, but I'm not as familiar in that front since I'm building my own GPU compute runtime in Vulkan, but it's still very much an experimental work in progress right now.
I wont argue that Go is the best. I will however argue how nice it is to be able to have extremely minimal distroless containers thanks to static Go compiled binaries.
Why should <.> <length> be less tokens than <RHO> <SPACE>? Obviously there will be examples that more obviously use fewer tokens but I’m not sure that the token efficiency stuff is that correct in general. Especially as it is often a lot more work to craft the correct APL for a problem than to craft suboptimal but correct python.
> Rust is the best because the LLM gets great feedback from the compiler because of its strong type system, and it can deal with the borrow checker for you.
> Javascript/Python is the best because there's so much code out there that the LLMs can train on, and LLMs can write lots of tests to make sure everything is correct. There's nothing to compile, so the LLM can iterate quickly.
My experience with Python is different. I noticed that my LLM (Claude) frequently gets stuck in cycles when coding larger features in Python.
I'm also doing a lot of work in Scala, a language where much less code is on the Internet, yet Claude does way better, it hardly ever gets stuck at all.
My theory is, that while there is a lot of Python code on the Internet, a lot of that code is crap. This will trap LLM's into coding crappy solutions which will eventually bite them in the tail.
Also, I believe that, especially for larger codebases, a strong type system helps not just humans but LLM's as well.
In fairness to the LLMs, many professionals do the same. And since it's trained on the source code of amateurs and professionals, it's not surprising when garbage in becomes garbage out.
Nice to hear that Scala has been working well for you, I've been wanting to try giving Gala to my agents to write with, it's a Scala-inspired lang that transpiles to Go that tries to cover some of Go's weaknesses (esp. for agentic development) while still retaining its compile speed, single self-contained static binary, standard library and backend libraries/ecosystem, and good tooling.
I don't understand how people have that stated as a fact. Coding was never the slow part. Neither pre-llm, nor now. How it needs to be done is software engineering. And that corresponds now to the "thinking" part of llms, so unless you are making the llm "think in lisp" its not useful. How would that even work. training data to be completely in lisp ?
> Lisp programs are often much more concise because macros let you abstract away recurring patterns and make them part of the language itself
functions ?
> So the bigger the program gets, the bigger the difference. In my own experience the apps I've built in Common Lisp end up about six to seven times shorter than the Python versions
By that logic writing code in this concept language made up completely of symbols would take you even further ( https://github.com/artpar/guage ) but in practice it doesnt because llms arent trained to that extent on this.
> I don't understand how people have that stated as a fact. Coding was never the slow part.
Because, for them (and me!), it was. I never understood why people kept saying that typing speed wasn't a problem since coding was never the bottleneck. It always was for me.
> functions ?
Macros can condense code down a lot more since you're essentially writing a language within the language.
> but in practice it doesnt because llms arent trained to that extent on this.
Right, but it probably works with words instead of symbols.
Is the speed of typing English a problem for you? Or is it that you have to think about which sentences to write and how best to express your thoughts. If you have learned a foreign language, think about the speed you express the same idea in said language.
At least for me, typing code (in a language I know) is as easy as typing English (which is not my native language, but something I’m reasonably fluent in). Thinking in terms of generic computing concepts is equally as easy. Most of my time is spent on not contradicting what has been written before, not on what I need to express. Because the software needs to be coherent.
So the speed of coding is slow, because of all the double checks I need to do. Not because I don’t know which statements to introduce next. And that thinking happens at a meta level (state and its alterations) not at the syntax level.
> Thinking in terms of generic computing concepts is equally as easy.
This is the trapdoor in this discussion. The degree to which a concept has been thought of in terms of generic computing (or just generically; modeled abstractly), before code is written, is different for every programmer. Not only is the degree different, but the deliberation and levels of awareness vary as well.
If your mental process explicitly models in the abstract, translating and typing out to code can absolutely feel like a bottleneck, and the specific language involved matters.
> If your mental process explicitly models in the abstract, translating and typing out to code can absolutely feel like a bottleneck, and the specific language involved matters.
Unless you're talking about raw typing speed or using an unfamiliar language, I don't think that it is. Most languages have few tokens and syntax rules. The next layer is the symbols from the standard library and the dependencies. After came the conceptual models, which is where the abstract thinking happens (like how does an hashmap works or what writing to a file entails).
Speed at the level of the first two layers can be greatly improved by very basic completion (syntax and symbols), or by just copy pasting. Integration like vim's quickfix or emacs' compilation mode helps because that's where compilation errors happens.
My opinion (anecdotally verified) is that people that feel like coding is a bottleneck have no editor fluency.
The degree to which my statement is true depends on the degree to which a programmer's abstract model incorporates _machine realities_. If your model takes into account the process model, and what is required for those process models to communicate, you are already modeling with mechanical sympathy. Programming languages (von nuemann languages) are all opinionated expositions of the same machinery, so the cost of translating the model to the language is a real cost.
My anecdata is the opposite of yours; the people that feel like the _actually coding_ is the bottleneck tend to be the people that have the _most_ editor fluency, as they are(were?) motivated to remove the bottleneck.
> Programming languages (von nuemann languages) are all opinionated expositions of the same machinery, so the cost of translating the model to the language is a real cost.
I don't think so unless you're talking about raw assembly. Even with C, you got structs (clump of data) and functions (which give us nice abstractions over pieces of logi) as well as syntactic sugar for branching and looping. OOP is a whole different model of design. And FP has a whole other basis of computation theory (evaluation and reduction from lambda calculus).
It's kinda like drawing in a sense. People think they know what something is and you ask them to draw a chair or a bike and they can't do it. So you ask someone to give you the specs of something like an attendance form, and they can't readily give it. Drafting the specs is the real cost, not implementing it.
> My anecdata is the opposite of yours; the people that feel like the _actually coding_ is the bottleneck tend to be the people that have the _most_ editor fluency, as they are(were?) motivated to remove the bottleneck.
If it were, we wouldn't have a drought of editor models. Vim and Emacs are decades old. Then VSCode is basically the same thing as sublime, kate, notepad++, and the various IDE out there. Coding isn't the bottleneck.
Some people do see typing English as a bottle neck, because they have so many thoughts running in their head so fast, they can't get them out on paper fast enough. The Typing is a bottle neck.
I've had that experience with programming also, where I know exactly what I want, and typing it all in takes extra time.
> I don't understand how people have that stated as a fact. Coding was never the slow part. Neither pre-llm, nor now.
This is what I’ve always maintained as well. More honestly I think there were two broad cases. First when the task is “copy this feature” coding can be clearly slower. Example when marketing says add a wishlist to e-commerce site. And when asked for requirements they say “just copy competitor.com”.
The other is adding features that require real decisions from people up the chain. A lot of us have worked on simple projects that should have taken a month that stretched on many months, sometimes to even being cancelled with nothing shipped. These are the one llms won’t help with.
Even the “copy this feature” scenario [0] can require a lot of implementation decisions, due to the feature having to integrate into an existing program and/or environment.
[0] which I only rarely encountered in practice — maybe because I never had to do marketing-driven work
I use agents heavily on Common Lisp in some parts of production projects. Typically Codex on whatever is the current top model with vibe-patched Tron MCP. REPL-based agent development via MCP does feel faster but I haven't ever bothered to benchmark it.
> Lisp programs are often much more concise because macros let you abstract away recurring patterns and make them part of the language itself
This is why Lisp never catches on. Each programmer invents their own ad-hoc, undocumented, barely working language in the form of those macros. The same thing happens in other languages with macros (like C and assembler).
I was referring to the macro preprocessor. C is a great language (although it is primitive by modern standards). The preprocessor is a separate and distinct language, not so good.
I have seen endless incomprehensible preprocessor crap. People have even used it for metaprogramming.
I used to use it for metaprogramming, too. One day, I decided to rip it all out and use the preprocessor as little as possible. The result was much better.
Analogous to C are the macro processors for assemblers. A friend of mine who worked at Microsoft told me about an assembler program that was about 50k. The person who wrote it had left the company. There was a bug in the program, and the manager assigned it to programmer after programmer, and they'd all give up on it after a couple weeks. So my friend said he'd fix it, and fixed it in a couple hours and checked it in.
The manager was surprised. He asked how my friend managed it. My friend said that the code was all macros to create a magical special undocumented half-assed language that nobody could understand. So he ran the object file through a disassembler (mine) to generate source code. Then the bug was obvious, he fixed it, and checked in the disassembly as the new source.
I've also heard complaints from Scala programmers about the overuse of macros making for incomprehensible code.
Empirically this is not true and the article overstates its claim by using "often", unless we take it to mean the tens of included macros that come with the language and aren't ad-hoc, undocumented, or barely working. (Or reinvented, coming with the language.) (Like the ones for defining functions, or classes, or structs, or looping, or multi-branch conditionals...) Take a random sampling of Lisp projects and libraries and you'll see the vast majority are using plain standard Common Lisp and lack a bespoke DSL. Some are more object-oriented, some are more imperative, some are more functional, they look pretty normal within each paradigm. The ability to easily create DSLs is indeed a nifty feature that can lead to the benefits described (https://www.stylewarning.com/posts/nbody/ is a post I've been fond of linking this year), along with the downsides, but that hardly represents most Lisp code. The feature wouldn't make a top-5 for reasons Lisp "never catches on", whatever that means. (Like the sibling I'm wondering if C is included in that assessment, or from another direction, Clojure.)
> The ability to easily create DSLs is indeed a nifty feature
That's the downfall I am referring to. The trouble happens when the creator of the nifty undocumented kludge language hands it off to someone else, who scraps it and re-implements it in Python or whatever.
I remember when C++ experts discovered "expression templates", which provided the ability to turn ordinary C++ code into a magical kludge language that did not at all behave like C++. It was all the rage for a year or two, then sank without a trace.
Nah it never catches on because of a combination of momentum, esoterica, being more difficult to learn due to having more discrete functions in the stl. And then there is the fact that it's pretty dated, so lots of people come to CL, get mad about it's warts and then become compiler writers.
I say this as somebody that loves CL dearly. The difference between princ, prin1 and print; set, setf and setq; =, eq, eql, equal and equalp is more than most programmers can be bothered to memorize.
So long as your feedback loop isn't slow enough to be taking you out of flow, it's fast enough. Having it be a few seconds vs a few hundred milliseconds mostly doesn't matter for a human, and will matter even less for an LLM.
On macros and DSLs, yes they're cool and even useful sometimes, but most of the software industry is working quite happily without them. And LLMs aren't going to change that because they are best when there's a lot of relevant patterns in their training data. That ends up being a far more important factor in their effectiveness than whether the language itself is token-efficient. It's much easier for an LLM to reason through how to do XYZ in Python where it already knows all the semantics and syntax than it is for it to do it in your DSL it's never seen before. To be clear, it can probably do both but will make mistakes an order of magnitude more in the DSL, and that's what will matter most for the LLM iteration time.
Lots of comments praising LLM's understanding of Common Lisp macros. I've seen them break badly at the macros-writing-macros level. I saw a current frontier model get so disoriented by one (a simple one!), it created something that expanded into a "(let (((".
(That's an impossible count (3) of parentheses on the RHS—like a six-fingered hand).
Funnily enough in computer science-land the often used model for transformsers corresponds to (heavily compressed) star-free languages, languages without counters, that specifically can't count things, like opening parentheses. This is rather theoretical though, because of the size of constants involved.
Yeah, but regular languages can handle parenthesised expressions up to some const parens-depth, which will be most of the code (for some small constant). And while it might be possible (not quite sure tbh) to express this in star-free manner, the expressions would explode pretty fast in size.
Not depth itself, but how using star you can get (ab)* in a much smaller expression than when you want to express it by forbidden patterns for star-free form. And if you nest it a few times r_to get to up to D depth[0] expression grows, but forbidden patterns would grow faster.
Are you seriously proposing that popularity == excellence is a thing on this planet? Please can you name a few examples? I can think of a bunch of counterexamples already...
I am not an expert, just an engineer with years of experience. I switched to Rust a couple years ago. I was still learning before LLM generated Rust was good enough that I stopped coding by hand.
Again, not a language expert, but: if you can describe your domain or business logic as clearly as a strongly typed language with a rich type system, most of your issues are gone. Enum and Struct with all the other core types plus the match expressions does most of my mental heavy lifting. I do not even track any of the recent language changes.
What I really care about is the shape of what I am describing - does it translate to code? How much do I lose in the translation? I want to try other languages, particularly Lisp but Rust is at this moment my choice. I have my own UI framework (1), my own provenance based business domain generator and a few simple language parsers.
I am building apps where you can, for example, throw a CSV file (2), ask questions in English and get answers - without using an LLM. Parser. Rust is no doubt a great language to express - not as a programmer, but as a prompter. I do not write the code. I ask LLMs to generate it using only basic knowledge of Rust and its type system. This will be the key to work with LLMs for majority of people.
I was expressing my opinion, not every statement is an argument, nor am I compelled to like whatever you do or whatever the author of the article wants to say; maybe work on not making so many assumptions.
> You can just point your LLM at the debugger, and it’ll make its fix and resume the program.
I'd rather my language surface problems at compile time (via type errors) for all possible code paths rather than a particular codepath at runtime.
That said, nothing prevents an LLM from controlling gdb/lldb either.
Also this is actually not such a big win: resuming the program after making the change aka hot reload is often a very hard problem even in highly dynamic languages like erlang and common lisp. What if the schema changes etc. ?
> To my knowledge Common Lisp is the only mainstream language that does all of this.
You should also mention racket, chicken scheme, guile scheme, MIT scheme, erlang/elixir, even Python via the repl and so on ...
> This is what makes macros possible. A macro is a function that takes your code and returns new code in its place, that means you can add new constructs to the language itself.
Macros + untyped code means the code cannot scale beyond a few thousand lines easily. Only a few programmers may understand the program fully and it’s often in their head rather than documented. Types implicitly document the code and allow hundreds of programmers to work on it. Lack of typing hinders code refactors too.
> Common Lisp is an ANSI standard and it hasn't been updated since 1994. I like this feature.
I like stable languages but not ossified languages. The internet was in its primitive infancy in 1994. This means Common Lisp may not be as web friendly as, say, golang without external libraries. Moreover, there has been a lot of progress in programming languages since 1994. OCaml/Haskell/Rust/Python/Ruby etc. incorporate some of that.
> But I don't think that's a problem anymore. Most programs today depend on millions of lines of code from packages that keep getting compromised. You don’t want that in yours. Also, with an LLM you could just write the part you need yourself or port the whole library — and LLMs seem to be really good at porting code.
You claimed earlier in the article that since Common Lisp was very concise you needed to spend fewer tokens via LLMs. But now that libraries for common tasks are not available you need to spend extra money on tokens to generate that functionality from scratch ! There goes your token budget !
Which is better ? A from-scratch LLM implementation of something with security holes or a library downloaded from the internet ? If you can restrict your dependencies to stable and popular packages from npm/cargo/pip you will probably be better off.
Lisp may not have the ecosystem to compete with Python or JS. On a tangent though, I am in the middle of a mini experiment with Codex that stemmed from trying to understand the beauty of SectorLISP and is now headed towards: "What would it take to build a toy OS for RISC V using successively richer evaluators?".
So...why would ecosystem matter now (serious question)? Given a big enough token budget, how big is the speedbump for "first...recreate all the libs that exist for Python, JS, whatever"? I mean...that is the point of all this, that we can now have the models grind out all the grotty bits in no time, and tinker-toy them together faster than we can.
1. Common Lisp has a whole system for defining and declaring types, and implementations do actually use this information to produce safer and higher performing code, including some amount of static, compile-time verification (e.g., you can get "expected an INTEGER but got a STRING" styles of compile-time errors). Fast, competitive-with-C floating-point math is achieved this way. DEFTYPE/DECLARE work.
2. If you prefer a system that doesn't feel like a 1980s barebones type system (i.e., Common Lisp's), then you can use Coalton [1] which adds types to a Scheme-like DSL within Common Lisp, but has a type system like Haskell's (similar to stock GHC + common extensions). Multi-parameter type classes, monomorphization, functional dependencies, etc. Yet it's still fully interoperable with Lisp code, uses the same Lisp toolchain, same Lisp compilers, same Lisp editors, etc. so it really isn't just a language atop Lisp, but something that's integrated within it.
I wasn't objecting to "a lisp with compile time enforced static types" I was objecting to "lmao just write it yourself". I'm well aware it already exists just as any number of basic bootstrapable C compilers do. It's a huge undertaking to bolt that on as a DSL from scratch instead of introducing a dependency.
Ah, that makes sense. I read the original comment, "you can define them," as meaning one can add static types to Common Lisp programs. But designing and building entirely new systems, at least ones that should work for a large class of practical programs, is not easy. I agree with you then.
CL already has strong types, it's static typing that it lacks. Though some compilers do make use of the information present to varying degrees, they'll emit warnings typically, not errors, for things like
(+ 1 "hello") ;; SBCL should emit a warning, but will let this run and you'll get a runtime type error
If it was not strongly typed then you'd be able to do the silly things you can do in JS and Perl like:
Yep. Or at least broadly the family of S-exp based languages. I've developed a (still closed source) Lisp dialect that has grown to what I believe to be the most(?) expressive and powerful programming language available today. As in, there is zero reason to ever pull out C/C++/Rust. It's fully compiled, with a system Fω typesystem, inductive types, and most importantly as of recently full whole program reflection. As in Lisps macro system extended to the further possible development. You can, quite literally in the language itself, define full compiler passes, manipulate the SSA, add/remove/analyze nodes.
(module reflection_symbolic)
(comptime
(fn sx-kind (x k) (sym= (syntax-kind x) k))
(fn sx-zero (x) (equal x '0.0))
(fn sx-one (x) (equal x '1.0))
(fn sx-add (a b)
(cond ((sx-zero a) b) ((sx-zero b) a) (otherwise `(f+ ,a ,b))))
(fn sx-neg (a) (if (sx-zero a) a `(fneg ,a)))
(fn sx-sub (a b)
(cond ((sx-zero b) a) ((equal a b) (syntax 0.0)) (otherwise `(f- ,a ,b))))
(fn sx-mul (a b)
(cond ((or (sx-zero a) (sx-zero b)) (syntax 0.0))
((sx-one a) b) ((sx-one b) a) (otherwise `(f* ,a ,b))))
(fn sx-div (a b)
(cond ((sx-zero a) (syntax 0.0)) ((sx-one b) a) (otherwise `(f/ ,a ,b))))
(fn sx-id (a b)
(if (and (syntax-binding a) (syntax-binding b))
(= (syntax-binding a) (syntax-binding b)) (sym= a b)))
(fn sx-lookup (x names vals)
(if (= (len names) 0) x
(if (sx-id x (head names)) (head vals) (sx-lookup x (tail names) (tail vals)))))
(fn sx-map (xs names vals depth)
(if (= (len xs) 0) (list)
(const (sx-expand (head xs) names vals depth) (sx-map (tail xs) names vals depth))))
(fn sx-expand (e names vals depth)
(if (> depth 32) (syntax-error "symbolic expansion exceeds 32 nested helper calls" e)
(cond
((sx-kind e 'symbol) (sx-lookup e names vals))
((not (sx-kind e 'list)) e)
(otherwise
(let ((xs (syntax-children e)))
(let ((op (head xs)))
(cond
((or (sym= op 'let) (sym= op 'let*))
(let ((ns names) (vs vals) (bs (syntax-children (nth xs 1))))
(declare (mutable ns vs))
(dotimes (i (len bs))
(let ((b (syntax-children (nth bs i))))
(let ((v (sx-expand (nth b 1) (if (sym= op 'let*) ns names)
(if (sym= op 'let*) vs vals) depth)))
(set ns (const (head b) ns)) (set vs (const v vs)))))
(if (/= (len xs) 3) (syntax-error "symbolic let requires one pure body expression" e)
(sx-expand (nth xs 2) ns vs depth))))
((sym= op 'the) (sx-expand (nth xs 2) names vals depth))
((or (sym= op 'f+) (sym= op 'f-) (sym= op 'f*) (sym= op 'f/)
(sym= op 'fneg) (sym= op 'fsin) (sym= op 'fcos) (sym= op 'fsqrt)
(sym= op 'tuple) (sym= op 'vec3))
`(,op ,@(sx-map (tail xs) names vals depth)))
(otherwise
(let ((f (fn-ref op)))
(if (/= (len (fn-params f)) (- (len xs) 1))
(syntax-error "symbolic helper call has wrong arity" e)
(sx-expand (fn-body f) (syntax-children (fn-params f))
(sx-map (tail xs) names vals depth) (+ depth 1))))))))))))
(fn sx-diff (e x)
(cond
((or (sx-kind e 'float) (sx-kind e 'integer)) (syntax 0.0))
((sx-kind e 'symbol) (if (sx-id e x) (syntax 1.0) (syntax 0.0)))
((sx-kind e 'list)
(let ((cs (syntax-children e)))
(let ((op (head cs)) (a (nth cs 1)))
(let ((da (sx-diff a x)))
(cond
((sym= op 'fneg) (sx-neg da))
((sym= op 'fsin) (sx-mul `(fcos ,a) da))
((sym= op 'fcos) (sx-neg (sx-mul `(fsin ,a) da)))
((sym= op 'fsqrt) (sx-div da (sx-mul (syntax 2.0) `(fsqrt ,a))))
((= (len cs) 3)
(let ((b (nth cs 2)))
(let ((db (sx-diff b x)))
(cond ((sym= op 'f+) (sx-add da db))
((sym= op 'f-) (sx-sub da db))
((sym= op 'f*) (sx-add (sx-mul da b) (sx-mul a db)))
((sym= op 'f/) (sx-div (sx-sub (sx-mul da b) (sx-mul a db)) (sx-mul b b)))
(otherwise (syntax-error "cannot differentiate this operator" op))))))
(otherwise (syntax-error "cannot differentiate this expression" e)))))))
(otherwise (syntax-error "cannot differentiate this syntax" e))))
(fn sx-partial (f index)
(let ((e (sx-expand (fn-body f) (list) (list) 0)))
(fn-with f 'body (sx-diff e (nth (fn-params f) index))))))
(macro derive-partial
(syntax-rules ()
((_ result source index)
(fnderive result source (transform (lambda (f) (sx-partial f index)))))))
Typesystems prevents hallucination errors and also ads an extra guardrail for the AI to adjust to. Its sometimes hard for the AI and humans to know what types are expected. The compiler holds the AI:s hand
You can point an LLM to a debugger in any language. You should measure token efficiency across different workflows to see if there is a programming language that is handled better by a LLM.
Give the same agent the same tasks across Common Lisp, Rust, Go, Python, Typescript, etc. Measure tokens, retries, failures, time to working solution, and human corrections.
This link is fishy. It appears to host TBLOP (The big list of porn) as some sort of invisible div before the actual article you guys read starts. Seems to be a weird form of SEO. Verify? Open the link with lynx, which does NOT honor CSS.
I have been experimenting with agents inside the application itself as it's being developed. The application has a harness and then that harness can self-evolve and evolve/introspect the application.
Even though I am of the opinion "Always has been.", this article, this thread, and even the code of the article, are demonstrations of why I am so disappointed with almost all of you and will not share my work until the situation improves, which seems unlikely to happen before death reaps this strange stalk.
The discoverer of Lisp spent some of his later years trying to get people to consider things like: what will be your response the first time AI kills a human child, yet despite nearly a century of fiction warning about the problems of technological progress, modern people have looked at or played through dystopian visions and responded "That looks fun. Let's try it.", not realizing that they won't be the hero, but rather the suffering NPC.
Assuming that for an llm all languages are ≈ equal, the developer factor matters most, and thus the language you know best is the best one, given your ability to correct, direct and orchestra the llm symphony. That been said, obviously this should be examined per task, as some aren’t capable of performing tasks other can.
I almost never read the code now so how well I know the language is pretty irrelevant.
What's most important is how well the language can give feedback to LLM while developing.
Second most important factor is how good technically is the final artifact.
Rust seems to be quite good for both.
Speed of iteration would be cool but LLM thinking takes the bulk of time anyways.
Live debug through something other than computer use would be cool as well but I don't know how well LLMs can use auch things for any given language. I just usually tell it to heavily instrument code and put in debug bridges in the app its building to inspect and ivoke stuff while the app is running.
I think LLM coding has changed the criteria from which language is best to which language has the best organizational/institutional support for the long-term.
I end up using Go for mostly everything, though, because a) Python is too slow and b) Rust will regularly fill up the hard disk of _every single one of my sandboxes_ with just... too much stuff, and takes around four times as much to compile (I can build, link, profile and fuzz Go in the same time I get a plain Rust build).
Still, I hope that one day I will be able to do just LISP :)
I don't think the conclusions of the article are actually supported by the arguments and evidence, and several of the technical claims seem pretty questionable (e.g. fast redefinition is not fast verification, entering a debugger is not guaranteed recovery, generated code is not automatically a validated library). With the same argumentation, Smalltalk (particularly Pharo), Racket, Julia, Elixir or even Forth would be equally credible candidates for "best programming language".
> And we're heading toward a world where software companies let their users change the product themselves, since with an LLM that's easy. So if a company builds a good opinionated domain language for its product, everything its users build on top of it will be much better, because they start from the company's opinions and not from scratch.
This is a bold assertion, and one I think undermines the merits of the argument.
"Change the product themselves" - I haven't seen any software company or discussion propose doing this. I'm not sure how it'll work. It sounds like it blurs the line between configuration (letting the user toggle certain classes of existing behaviour) and forking (the user now owns the contract with the product).
Perhaps the idea is something like an embedded scripting language as used in some games, where users can write custom snippets to glue together what they want. Or a relational language to generate custom visualizations from raw data. These are the only forerunners of the concept that have found actual market fit to date that I'm aware of.
It's hard to generalize where else this would be useful outside of these domains, and in any domain I can think of - UI interfaces, data analysis, procedural generation - there is already a dominant language runtime bound to the environment (Javascript in browsers, C# in Unity) exposed to customers. Would Common Lisp be able to displace them? If so, why and how would it improve?
Merits on the basis of the runtime itself are meaningful, but the runtime needs to be popular for the value of the PL to be seen.
But end users are using LLMs to write their own code.
It's more like the new Excel. End users used to get a data dump from SAP,ERP or something, and use Excel to manipulate. Now they use the LLM, and are also using LLM to build large cobbled together messes.
Excel is the bane of accuracy, any miss aligned row in a calc and suddenly a company is loosing billions. LLM's are this on steroids.
I would think that common lisp being image-based and so therefore having tons of implicit state that is not manifest in code is a huge downside to programming with an LLM since the state of the system is not easily reproduced from program text.
Common Lisp isn't image based. I mean, you can store images (although that's not a standardized thing), but that's normally what's done. In the normal workflow code is in files that are compiled and loaded into a running image; changes are made to the files and then they are recompiled as needed (using dependencies between the files to do only the files that have to be recompiled/reloaded).
Why wouldn't you have code backing the image? Sane people do. The main exception is when you are working on code you know is meant to be thrown away (testing out a library for instance, but not building your actual application).
This is a terrible take. Completely incoherent and nonsensical. Lisp is specifically LESS useful for LLMs because of the added complexity of its runtime.
The piece ostensibly argues why lisp is ideally suited for LLMs when in fact it's really arguing why it's great for humans. The machines really don't care.
[OP] misterchocolat | 18 hours ago
sroerick | 17 hours ago
tzmudzin | 17 hours ago
DSL presumes agreement on semantics, and that's often the most difficult part.
sroerick | 16 hours ago
tzmudzin | 15 hours ago
- economies of scale no longer work, and you end up doing a custom ERP for your business from scratch.
- your business changes might invalidate your model quickly. You sell through distributors, but open an online shop -- and suddenly your customer is not one of few dozen well-known businesses with a known address and tax number, but user2252 who bought something late at night last night. And you want to understand the needs and behaviors of both.
sroerick | 15 hours ago
tzmudzin | 15 hours ago
- for the economies of scale, you might develop your custom solution 20x faster now, but you're still in competition with the established provides with templates for most of the cases (who btw have the same LLM capability at their disposal)
- for the change in business -- you can ask an LLM which changes this induces, and it will give you most typical impacts. And then you're back at square deciding if it's better to roll your custom DSL and the custom system downstream, or just use off-the-shelf stuff that covers 95% of it from day one (well, maybe day two or three)
sroerick | 9 hours ago
whartung | 16 hours ago
When it comes to back office business programming, there’s just a lot of code tasked with copying a litany of bits of data from one structure to another.
Whether it’s copying a web form into a database, or converting Their JSON to Your JSON, it’s a lot of detail that does not abstract well. It’s all shapes and sizes and formats, and it almost always has to be enumerated in excruciating detail and, typically, twice.
Sure, there’s logic and whatnot involved, but it, too, is specific to some subdomain of the larger system and it, too, does not abstract well. Not in the large context of the overall system.
Accounts Payable and Accounts Receivable, at 10,000 feet look almost identical. They’re almost literally the same thing with the sign flipped. But in practice, they don’t share code well. You end up with two similar systems, but not similar enough where sharing is actually worthwhile.
At best they can leverage a common API to the GL.
Turns out a lot of languages can manifest a decent level of abstraction. But even then, folks push back.
Consider the love/hate relationship with ORMs. Or the annotation driven markup in Java programs and the underlying “magic” that they enable. Like scribing mystic runes onto things.
Those are both very powerful, yet folks experience that and toss their hands in the air and throw out the baby with the bath water and jump into something “magic free” like Go.
Just because you can use something like CL to “make your own magic”, doesn’t mean it’s a good idea. Doesn’t mean it scales. Doesn’t mean it communicates well to others. AI or no.
It’s not the AIs world yet. We already know that if the AIs want a better language suited to AI efficiency, they’ll come up with their own. I’ve already seen crass examples of “code only an AI could love”. Completely impenetrable, at least to me. May as well have represented it as a color image and collection of RGB values. Opaque to me, but the AI could “read” it.
There is much more to programming and systems than token density, and AI is still getting cheaper by the day, so less reason to even pre-optimize for it anyway.
sroerick | 16 hours ago
While I am extremely taken with Lisps and the lisp way of doing DSLs, I would probably go with an OCaml to make a DSL for a company specific ERP. It seems a better way to go about the problem.
Lisp, on the other hand, I have found to be extremely good at domains which seem the same but which are tremendously different. For example, a workout app is a surprisingly complex domain. Different exercises have different storage models and functions, as do different training sessions and different programs. Rather than try to build a monoprogram, one training app to rule them all, I find lisp wonderful for making "microprograms".
This bears resemblance to Accounts Payable and Accounts Receivable but I don't think Lisp would be a good fit for those. Perhaps a Lean or a Rocq, something with proofs.
> We already know that if the AIs want a better language suited to AI efficiency, they’ll come up with their own.
My agents seem to really like Tree Calculus and have bullied me into working on a language which uses it.
frollogaston | 17 hours ago
darkwi11ow | 17 hours ago
frollogaston | 17 hours ago
rmunn | 17 hours ago
Now, if your entire server is taken down because one connection threw an exception, that's bad design. But pretty much no major language works that way. All of them allow you to set things up so that an exception handling connection A won't affect connection B. And if you've done that in Common Lisp, then connection A halting and waiting for the debugger won't affect connection B either.
frollogaston | 6 hours ago
Anyway it sounds like this isn't really the target use case for the debugger since restarting a webserver is supposed to be easy. Maybe there's a different use case in mind?
vindarel | 10 hours ago
Also of course the default is to not get the debugger but let the server thread crash and print the backtrace. With a user setting, you can choose to get the debugger, and have this request wait (the connection may time out, which isn't an issue during development). Another setting is to print the backtrace in the browser (= dev mode).
Jtsummers | 17 hours ago
vindarel | 10 hours ago
Python REPL and CL's are very different!
In CL: you can install new dependencies from the REPL. You can change a class definition, the existing objects will take the changes at the next invocation. You can control how this happens, etc. TLDR; CL is built around live programs. Buuut we can also do it the dumb (and safe) way following the industry's best practices.
mark_l_watson | 9 hours ago
Yes. I code Python in Emacs with the ancient Python support for running a REPL, loading buffers, editing a function and just hot reloading the modified function, etc.
I am an old man, and I like old man tools :-)
vindarel | 8 hours ago
randallsquared | 9 hours ago
vindarel | 8 hours ago
> can be useful in extreme situations
it's already useful in development, it's one of those things that shorten the dev loop and make development in CL a breeze. For production, you choose. Connect to the live image to inspect the state without modifying anything or debug while it's live.
> recorded nowhere
you can connect to a running program and have the source under VCS. The thing to do isn't to copy-paste new function definitions in the live image's REPL, but to connect to the image, make changes to the source files, and re-compile them (a C-c C-c in Slime) (sending changes to the running program).
> safer
yep, some things are safer. Advanced features are useful even for simple things (introspection, etc).
frollogaston | an hour ago
rmunn | 17 hours ago
https://comp-348.github.io/lisp-debugging.html has an example of what it looks like. A toy example, to be sure, but the basics of a real example would still look the same. You're given a choice of several options, very reminiscent of the "Abort, Retry, Ignore?" choice that used to be oh-so-familiar in the days of DOS. Except this one is more useful, because it offers ways to specify how to resume. E.g., the toy project is halting on a `(print X)` call where the value of X is not defined. And the choices are:
0. Continue. (Retry using X).
In the toy example, this would fail, because nothing else has defined X. But in real code, the name might have been undefined because the data needed to define it hadn't arrived yet, from the database or the filesystem. In which case retrying the statement might work the second time.
1. Use-value. (Use specified value).
This one prompts you to enter a value for the undefined variable, and continues, but it does not modify the value of X in the program. The next time the program tries to use X, it will halt again with another "unbound variable" error.
2. Store-value. (Set specified value and use it).
This one, just like Use-value, will prompt you to enter a value to use... but then it will set X to that value and continue running the program. Next time the program tries to read the value of X, it will have one, and the program won't halt.
3. Abort. (Exit debugger, returning to top level).
This is what you would choose if there's no good way to fix the error, and you just have to quit the program and restart. Though note that choosing this option isn't going to exit the program you're debugging, just take you out of the debugger. You'll still need to kill-and-restart it some other way... or come back an hour later when the value is finally available, and then choose options 1 or 2.
Hopefully that gives you a taste for what the CL debugger is like to use in practice.
frollogaston | 17 hours ago
rmunn | 17 hours ago
And it's not editing files on the server, it's actually reaching into the running code and tweaking its values.
That, I think, is the difference here. In many languages, the debugger can pause on the exception and let you inspect the code. But in every other language I've used, once you edit the code to fix the bug, you can't resume from where the debugger paused. You have to recompile the code and resume from the top. In CL, you can resume from exactly the state you were in when the debugger paused, only this time with the correct data in place. (Or even with a code fix having been applied, live, to the code).
brabel | 11 hours ago
Second, other languages can also do that, Java for example has facilities to recompile functions and then rewind the stack to run the function again from the beginning of it. Dart also does that even better and people use that extensively in Flutter with hot reloading. I do that in a debugger during tests though, never seen it done in prod.
chrchr | 17 hours ago
So, in practice, it might look like you hit any kind of runtime failure, and then the LLM writes some code to fix it, and the user's request completes successfully with no errors.
invalidOrTaken | 17 hours ago
1. Have you had much success w/moar macros in the age of the LLM? I've been impressed by the models' ability to write good ones, but I can tell my taste/judgement for macros isn't quite there. But they tend to be pretty good at writing gnarly ones, and I would love to work more macros into my workflow. Would love your thoughts. (Have I taken "Simple Made Easy" too far and left macro value on the table?)
2. Do your models ever get confused with image-based dev, and state? It's seemed dumb to me to have models keep running `sed` to change files, but it is nice to have a human-readable, filesystem-backed record of definitions. Would love to hear your experience here.
[OP] misterchocolat | 17 hours ago
I've seen a big improvement in LLMs writing macros since Opus 5.5 came out. What really helps I think is that I've written skill files with my own examples and instructions.
Same thing for image-based dev. without an agent.md file with good instructions on how to work with a live image it will do dumb things. this sort of workflow is jsut so far off the training distribution.
I think what changed recently isn't that LLMs got better at CL, they just got way better at taking my skill/agent.md files and reasoning through them.
TacticalCoder | 16 hours ago
There shall be one minimal, ultra-hardened, tiny attack surface, "majority gate" picking the answers that most implementation agrees on.
This shall not only detect a great many implementation issues but also it'll help find security issues and platform defects (say the Common Lisp, Haskell, Rust and Python all agree but the Java one fails: in rare case it'll be due to a JVM bug and finding that out shall be simplified).
Code shall be generated from specs in n languages and ran on n stacks. The gate shall return the answer as soon as a quorum is met and, later on, any bogus answer arriving shall be cause for enquiry.
We'll have such systems, it's just a matter of time.
magnusi | 13 hours ago
I'm the author and I share the same philosophy, hahaha, my users told me about this post
fragmede | 13 hours ago
ellg | 17 hours ago
I dont really understand what macros get you when llms exist since a llm doesnt really need to create dsls to get work done
[OP] misterchocolat | 17 hours ago
UncleOxidant | 17 hours ago
ellg | 17 hours ago
I agree with your point
invalidOrTaken | 17 hours ago
There is also a phenomenon I've named "brevity collapse." Often, when shrinking a codebase, you find you need less glue. Additionally, because it is smaller, you can hold more of it in your head and see more opportunities for shrinkage. Surprising things happen when the bones of your language get more efficient---it's more like going from elephant to flea than elephant to grizzly bear. The smaller scale means there's less "overhead" code, which means you can go smaller still.
jaggederest | 17 hours ago
0xpgm | 17 hours ago
Let's way you wanted to do a web application using LLMs. Using a web framework would cost less tokens than using the vanilla underlying language (Python, PHP, whatever..), which is again way less tokens than building up from assembly (an LLM should be able to do this given enough time and compute).
dang | 16 hours ago
https://news.ycombinator.com/item?id=21232352 (Oct 2019)
https://news.ycombinator.com/item?id=4766191 (Nov 2012)
https://news.ycombinator.com/item?id=694700 (July 2009)
* in the older sense of "token", meaning that one measures a program in AST size rather than lines of code
---
Edit: ok, this is what I get for not reading the article:
> For LLMs, less code means fewer tokens, and tokens are what you pay for so you spend less on development
0xpgm | 10 hours ago
copperx | 15 hours ago
Karrot_Kream | 16 hours ago
There's a "zen" moment felt by folks who've written lots of macros (experienced in Lisps and Forths) where, when designed properly, you really feel like you've "grown" a language and have really walked up the abstraction ladder. My thesis is that macro heavy code when the author designs the macros well are very readable. That an agent's output when stacked upon macros can be a lot simpler to read and understand than in languages where the syntax is less fungible. And if you leave a project for a while and come back, an agent is a perfect tool to help you read your macros and familiarize yourself with the abstraction surface again.
Just a theory though.
cryptonector | 8 hours ago
Pithiness. A human might need to be able to read and understand the code. If the code is much longer than it could have been, it will take much longer to read and understand it.
VikramBhamre | 17 hours ago
[OP] misterchocolat | 17 hours ago
ehe78qhe | 17 hours ago
Partly, having to do my own pentesting and red team work instead of using someone else's work that has already been pentested.
frollogaston | 17 hours ago
Jach | 15 hours ago
From the parent's security standpoint I'm more sympathetic, there's been a lot fewer eyes on CL code, there's no central place to keep track of discovered security issues, and perhaps more vigilance is required against untrusted input compared to other languages. At the same time, every time I've exposed a CL-powered website I've noticed various attempts at e.g. wordpress endpoint discovery and I sleep soundly knowing a wordpress deployment is something I'll never have to worry about or take extra precautions against. Every time I hear about a supply chain attack I also am happy about the choice of CL. (Though in truth it's not to say that such attacks aren't possible, but for various reasons, one of them rather quite embarrassing to the overall ecosystem, pulling a big one off is going to be more difficult.)
mstep | 17 hours ago
giancarlostoro | 17 hours ago
> To my knowledge Common Lisp is the only mainstream language that does all of this.
Sounds like someone who has never used C# and Visual Studio? Even JavaScript is capable of doing this, honestly JavaScript might be the one language with the richest developer tooling of all time (possibly?), sad to say because there's nicer to work with languages out there.
frollogaston | 17 hours ago
giancarlostoro | 17 hours ago
frollogaston | 17 hours ago
rmunn | 17 hours ago
https://news.ycombinator.com/item?id=49974251
giancarlostoro | 17 hours ago
To be fair, I love Lisp, I dont do a lot with it, though I'm mostly a fan of Racket which is the most modern one outside of maybe Clojure.
Capricorn2481 | 17 hours ago
But I'm not positive the juice is worth the squeeze, and this article was unconvincing. I mean if you want macros and terse code and a bigger ecosystem, wouldn't Clojure make more sense?
anonair | 17 hours ago
(not a CL expert here)
frollogaston | 14 hours ago
hajile | 10 hours ago
vindarel | 10 hours ago
rootnod3 | 17 hours ago
whizzter | 9 hours ago
Visual Studio has done this since the 90s for C++ code (and C# code for a good while also, just most people have debugger exception catching turned off).
How I know? Back in 2000-2001 we were working on a Dreamcast game with a 3dfx/Glide PC renderer, that 3dfx driver on WinNT was unstable and crashed the machine every 3rd-5th start of the game so being able to edit-continue was a damn lifesaver since a lot of common tweaking of custom stuff outside of what connected to the scripting system could be done at runtime.
This is one thing I think so makes people loyal to MSVC despite it having been behind on standards (gonna be interesting to see if they get reflection up to speed for the 2027 release, GCC's there but not yet Clang).
rootnod3 | 6 hours ago
The leave instruction is required to exit a try, catch, or a filter block. At least last time I read the spec (which is a while ago, but still).
See https://news.ycombinator.com/item?id=23844283 for examples on CL.
jaggederest | 17 hours ago
sroerick | 17 hours ago
dang | 17 hours ago
I shouldn't have been surprised, because that part of macro writing is purely mechanical syntax transformation—just a "take these tokens and return those tokens" function that one should expect LLMs to be good at. But I was surprised, because for me that was always the hard part.
So now I can think up new macros to abstract over patterns in my code—the part of macro-writing that I enjoy—and then push a magic "implement this" button to make it work.
What I'm not sure of yet is whether this is an evolutionary dead end—a train stop on the way to "you'll never look at code again, so what does it matter what programming language you used to use". Yes, I still look at my code, and this is a pretty nice train stop, wherever the tracks lead to.
pfdietz | 8 hours ago
6gvONxR4sf7o | 17 hours ago
ducktective | 16 hours ago
Can lean generate small static binaries the same way Go/Rust/C/C++ can?
How practical is rewriting, say, grep in Lean?
hargup | 15 hours ago
> Can lean generate small static binaries the same way Go/Rust/C/C++ can?
Lean compiles to C and the binaries aren't huge, though haven't benchmarked this part yet.
> How practical is rewriting, say, grep in Lean?
Very, you should probably try it.
floydnoel | 10 hours ago
6gvONxR4sf7o | an hour ago
As for library support, yeah it's definitely not a mature ecosystem. But since it compiles to C, the FFI is great, so you can just ask an LLM to write bindings to your favorite rust/C/C++ library and they do just fine. I recently wanted (apache) arrow bindings for lean, and LLMs knocked out perfectly sufficient bindings just fine.
gr_norm | 16 hours ago
hargup | 15 hours ago
ltbarcly3 | 17 hours ago
Maybe you could take CL as a foundation, introduce modern features and uniformity to the language, remove some of the insane complexity, tame the unhygienic macros, and come up with a pretty good language. Since about 7392 different flavors of scheme have tried to do this and mostly failed, I think this is very unlikely.
gorgoiler | 17 hours ago
False. A partial ordering doesn’t guarantee a maximum element!
[OP] misterchocolat | 17 hours ago
DonHopkins | 12 hours ago
So Lisp is better than itself.
If the extension improves something without making anything else worse under the chosen criteria, that's a Pareto improvement. Otherwise, it's a tradeoff.
https://en.wikipedia.org/wiki/Pareto_efficiency#Pareto_order
mumin00 | 17 hours ago
BurnerBurner | 17 hours ago
SatvikBeri | 17 hours ago
dang | 17 hours ago
Karrot_Kream | 17 hours ago
andrewstuart | 17 hours ago
There’s the right tool for the job, there’s compliance with requirements, there’s personal preference.
Don’t let anyone ever tell you you are programming wrong.
fithisux | 17 hours ago
https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
But I find ideas like Carp very attractive.
Still common lisp is designed very good.
hajile | 10 hours ago
CL is very competitive with popular managed languages like C#, Go, Java, etc despite those all getting tens to hundreds of millions in investment while SBCL gets basically nothing (imagine what it could do with serious investment).
nurettin | 17 hours ago
drivebyhooting | 17 hours ago
literalAardvark | 16 hours ago
The models learned to code, the actual language is just a tiny adapter on top.
Jach | 16 hours ago
alexjurkiewicz | 17 hours ago
I think others have pointed out that most modern scripting languages can halt at exceptions without unwinding the stack. Python & Node both support this with core tooling.
As for DSLs, they constrain the LLM which generally helps with code quality. However why implement your DSL in the unconstrained chaos of CL? You can write DSLs in Rust which gives you static typing, a borrow checker, and clippy.
marviter | 16 hours ago
- Conditions[0]
- Evaluation and Compilation[1]
I use both extensively while developing, debugging and analysing. With SLIME (this is a standard and very very slimmed out development aid) you add a debugger hook that allows restart, return from, move down, move up and etc into your available RESTARTs.
[0] https://www.lispworks.com/documentation/HyperSpec/Body/09_.h...
[1] https://www.lispworks.com/documentation/HyperSpec/Body/03_.h...
baq | 16 hours ago
cyberax | 16 hours ago
gblargg | 16 hours ago
baq | 15 hours ago
(It’s also essentially pointless when your unit of deployment is a disposable container and not a long running lisp system, but it at least makes development a bit nicer)
layer8 | 10 hours ago
This is like claiming that you don’t have type errors in Lisp because everything is a list in Lisp. In reality type errors will either manifest in different ways, or still explicitly occur [0].
[0] https://lisp-docs.github.io/cl-language-reference/chap-4/e-e...
swiftcoder | 14 hours ago
This is where the functional programming model really shines - there typically isn't a bunch of hidden state floating around to break hot-reloading
armitron | 12 hours ago
So no, the magic sauce is not “functional”, it’s that CL stemming from the Lisp Machine paradigm is designed for that sort of experience whereas something like Python very much isn’t. And it shows (there’s no image based development in Python that doesn’t end up creating more problems than it solves but image based development is not only the norm in CL but also tremendously empowering).
FrustratedMonky | 8 hours ago
Are you sure this isn't just doing functional programming, and not realizing it?
armitron | 4 hours ago
Quitschquat | 7 hours ago
It works fine and was designed for this use case.
sheept | 13 hours ago
Web development supports hot reloading modules, but this is effectively just dynamic imports and relies on the app being written for a library/framework that encourages pure code; it doesn’t kill the old module.
atilaneves | 12 hours ago
It also makes your agent wait for the compiler to finish.
Karrot_Kream | 17 hours ago
sroerick | 16 hours ago
Karrot_Kream | 15 hours ago
sroerick | 15 hours ago
Webpages, zettelkasten, todo, workout, diet tracker. It's a web framework! Creating an endpoint is just like making a function in emacs.
Since lisp is homoiconic the AST is just raw JSON. I save the AST in JSON stores in Postgres. But you can clone the AST down and then eval against a local copy of the REPl. So you get local eval for free.
Of course you have to rebuild git to manage a branching REPL in this way.
sroerick | 15 hours ago
Folks have found it useful to just point gippity at the page and ask questions
vegnus | 7 hours ago
phillc73 | 15 hours ago
Janet for orchestration, C for the frame loop.
[1] https://github.com/jennystats/janet-num
JonChesterfield | 2 hours ago
bitwize | 17 hours ago
Astronaut 2: Always has been...
shikck200 | 16 hours ago
djtango | 16 hours ago
Culturally, I feel a place like Valve could make it work but you could turn around and ask why does an org need to be in service to and arrange itself around a tool.
reikonomusha | 5 hours ago
rspeele | 16 hours ago
In my circles I've noticed it's very easy for us to rationalize why our previous favorite language is also the perfect language for the agent era.
If your favorite language before was Python, why, LLMs are fluent in it! So much training data! So many libraries! Home of machine learning! None of that pesky compile time, agents don't need compile time safety anyway, they write such good test coverage! It's The Perfect Agentic Coding Language.
If it was Rust, by jove, an agent can easily handle the headache of satisfying the borrow checker, and now you get the best of all worlds! Safety! Near-C runtime performance! Abstractions! The only reason people didn't use Rust before was it was Too Hard and there were Too Many Furries and now it's not hard and you don't have to interact with them, so get on board. It's The Perfect Agentic Coding Language.
If it was Golang, oh my goodness, what a choice. Pretty fast compile time and pretty fast runtime. Agents get a tight feedback loop with build->run->test->edit. Not very complicated, code has to be written in a straightforward banging-rocks-together way. Good stable ecosystem! Rob Pike designed the language for people he said were "not capable of understanding a brilliant language but we want to use them to build good software." That's an arrogant, demeaning way to describe your colleagues but if they're LLM agents it's dead on! It's The Perfect Agentic Coding Language.
I could go on and on. I'm not immune either! My own favorite language is F# and when I feel like self-justifying, I play the same game:
It has access to the .NET ecosystem like C#, but I don't have to constantly remind the agents to prefer a style with immutable data and pure functions, they idiomatically do that in F#. Files have to be in order and can only refer to symbols defined "earlier" in order, if you want mutually-referential types or functions they have to be declared as such in a joint statement, so spaghetti is hard to create: each project's codebase naturally ends up in a layered bottom-to-top architecture. The language is terse enough to be token efficient, without being symbol soup. FSX scripts can be generated during agentic code reviews to demonstrate repros for discovered issues. If there's any type of code that still warrants me jumping in and writing some myself, that code would be data type definitions/domain modelling, and F# is a joy to write those in. It's The Perfect Agentic Coding Language.
tommica | 16 hours ago
Could be said that it was the path of least resistance, but the funny thing is that most of that resistance is you just being in your own way.
mod50ack | 16 hours ago
Use the language that you like (enjoy your favorites!) and that binds well with other tools you're using. I'm currently working on a project that's all C++ (wxWidgets frontend, plus C++ backend). I've used LLM tools with it, no issues. Would be the same with any other language, from what I can tell. I have another project in Go I'll probably try it on at some point soon.
sroerick | 16 hours ago
insin | 16 hours ago
sroerick | 16 hours ago
I think Lisp and an ML are kind of the dynamic (static) duo. I still think MLs are the best for complex projects, but interpreted languages are pretty neat too. Python, Rust, and Golang are all great, also.
I think a tree calculus language might be the actual best - but it's too soon to say
Really, languages are great for LLMs.
prophesi | 16 hours ago
This isn't the future I wanted, and is why I advocate for trade schools these days.
I miss the days I could listen to music and use my adhd/autism to its fullest potential reading documentation and figuring things out myself.
Of course, these are same people you'd want managing these AI agents in the first place, but reviewing code written by others always kind of sucks, especially when it's assumed the language model knows better than you do which isn't often the case!
Was the PRD perfected on the requirements? Only God knows, and I personally want to be there when it's written.
ChadNauseam | 16 hours ago
sshine | 16 hours ago
So picking languages for their compilation and especially runtime properties is key.
What I've learned, though, is that languages with reckless error handling produce more errors at runtime. My Rust programs just don't crash because I don't need it to be explicit about reaching a "total" approach.
If you're in a C# environment, I see a case for F#. And if you want fast but more solid than Python, why not Mojo? Although who reads code.
This article convinces me. But I suspect like my lack of domain knowledge of Lisp makes me spend time learning the runtime: how and when can I switch to JIT, what's the async story, how does the harness become part of the Lisp program, etc.
literalAardvark | 16 hours ago
It's probably the best language for AI coding though. By far, as far as I can tell.
aussieguy1234 | 16 hours ago
hota_mazi | 16 hours ago
nickm12 | 16 hours ago
I've heard fans of both statically typed and dynamically typed languages advocate for their language. The static type fans say that the rigorous compilation process gives the LLM a fast iteration loop with clear messages from the compiler on what invariants aren't being upheld. The dynamically typed languages advocates talk about fewer tokens, the popularity of the language in the training data, and so forth. Guess what, these are the exact same arguments these communities made for human developers.
Personally, I don't know the answer. The industry has swung back and forth on this over the decades. Before AI code-gen static languages were on the upswing for a variety of reasons, including runtime efficiency and much better ergonomics thanks to modern type inference engines. I suspect those reasons are still valid, and also that statically typed languages give LLMs a leg up because it is easier to reason about them locally thanks to declared types and information hiding.
sroerick | 16 hours ago
thenoblesunfish | 16 hours ago
stackghost | 16 hours ago
The de facto open source implementation, SBCL, has a solid compiler and garbage collector. It produces fast code.
regularfry | 12 hours ago
A couple of years back there was a paper doing the rounds about software execution efficiency, which a lot of people in my neck of the woods got interested by because of the potential for environmental impact at the sort of scale we operate at. SBCL ranked disturbingly highly for something that is culturally never going to happen for us.
Jach | 16 hours ago
onion2k | 16 hours ago
I don't think that's strictly true.
What you need is for your LLM to be able to understand enough context to be able to make a change with as few token as possible, so if your code isn't expressive enough or if it has a tendrils calling lots of different functions/methods all over the place, then you'll have to give it much more code (context) than if you've got nice encapsulated modules that don't depend on other parts.
The design of your architecture (probably?) has a greater impact on token use in a large app than the language it's written in. Although, obviously, languages lend themselves to particular architectures so it's correlated.
asxndu | 16 hours ago
Archit3ch | 14 hours ago
seer | 16 hours ago
I do think this is an unrelated win of functional languages that hasn’t yet been “discovered” by the vibe coder crowd - FP’s whole premise was that it makes your code depend on much much less things so you can “fit it in your head and reason about”… that’s like the perfect sweet spot for agentic as well, we just haven’t seen tools utilize that in earnest.
groestl | 16 hours ago
If that would be possible, there would be no function signatures.
fc417fc802 | 14 hours ago
skydhash | 11 hours ago
Why would you do that? Take common lisps, most of the functions has been standardized for ages. It’s like saying by slightly adjusting the rules of addition, you can break maths.
Something that is going to be used all over the codebase is basically axiomatic and needs to be written and modified carefully.
fc417fc802 | 9 hours ago
Are you really asking why someone would find themselves needing to tweak the edge case semantics of a widely used function in a large project?
> needs to be written and modified carefully.
But notably compile time static type checking greatly reduces the risks involved. That's all I was trying to say.
skydhash | 9 hours ago
If you do this and breaks the assumptions of the interface, it’s just recklessness. You don’t go and tweak functions without fully understanding the rules it established and how those rules are treated as axioms somewhere else.
> But notably compile time static type checking greatly reduces the risks involved
This is usually an excuse to not fully check the assumptions in a given piece of code. Like tbe warnings will be enough to take care of any issues. Type checking is not software correctness. I’ve seen badly defined type, type erasures, and various other issues that negated any value brought by the type system.
regularfry | 12 hours ago
Models aren't good at this by default, so you end up with spaghetti mess. But if you tell them that you want strong interfaces, isolation, and types, they can work that way.
teleforce | 16 hours ago
Recently there's an article on language plasticity in the era of AI/LLM and why D language is very well suited for this era [1].
Perhaps we need a proper benchmark similar to Beaver but for AI assisted coding for different programming languages instead of Text-to-SQL [2].
[1] Language Plasticity is More Important Than Ever:
https://blog.dlang.org/2026/08/10/language-plasticity-is-mor...
[2] BEAVER: An Enterprise Benchmark for Text-to-SQL:
https://beaverbench.github.io/#overview
DanHulton | 16 hours ago
onion2k | 15 hours ago
bobanrocky | 16 hours ago
cissikatt | 9 hours ago
SolubleSnake | 16 hours ago
The main things you want for ERP are just to simplify database interactions as far as possible and to give you as many and as customizable options as possible for data visualization and curating information for a non techie user. I dont really get why LISP?
copperx | 15 hours ago
qalmakka | 16 hours ago
nylonstrung | 16 hours ago
groestl | 16 hours ago
georgemcbay | 15 hours ago
One could argue that this was a large benefit for human coding long before LLMs became useful.
It is why I always preferred strongly-typed languages.
And once we had practical strongly-typed languages with implicit type inference I really couldn't wrap my head around why anyone would prefer dynamic typing other than just inertia due to that being what they were used to.
WalterBright | 15 hours ago
The best dual path systems are when they use different technology. Hence, an error in one is highly unlikely to infect the other.
This is what static typing provides.
ozim | 15 hours ago
jessekv | 15 hours ago
kjs3 | 9 hours ago
WalterBright | 5 minutes ago
I encounter that regularly, as I often use aviation analogies to guide me in programming. D's design has been significantly influenced by my aerospace experiences.
WalterBright | an hour ago
IshKebab | 15 hours ago
Previously studies into the benefits of static typing for humans were always a bit flawed because you can't do that with people. And tbh I don't know why but there are a surprising number of people that don't appreciate static typing. My guess is a combination of ego and laziness, which doesn't apply to LLMs.
sheepscreek | 16 hours ago
I’m not sure if the Common Lisp compiler can be very helpful either, since it’s a dynamic language and A and B could be many different types (duck typing).
Strong types are the way to go, at least for now.
Jach | 15 hours ago
Type declarations are also optional and compilers can create compile-time warnings about them. Thus for many trivial cases, when using SBCL some obviously wrong types, or typos, or miscounted arguments, can be caught ahead of time without having to execute code. CL is also not duck typed. If abc-xyz is a generic function, selecting which method to call relies on the actual class hierarchies of the given A and B objects, there's no "duck shape" shenanigans.
For static types, well, CL is flexible enough to bolt such a system on top as a library, where you'll have a full ML/Haskell style type system. https://coalton-lang.github.io/ But it seems the relevance for LLMs is rather mixed, much like studies from the last few decades on static/dynamic typing in general: https://danluu.com/pl-tokens/
xlii | 15 hours ago
It's possible to write the whole system only by defining the types.
The "glue" can be sloppy but as long as it keeps on the edges the output is most often fine.
Recently I'm on the fence about Rust vs OCaml (but plan to write about it soon) because I have ~700k LoC in Rust but my workflow starts to get seriously dragged down by compilation/tests in isolated worktrees.
I recently also dab with Gluon (as embeddable type safe scripting) and rule-based-development for maximum code control/agents output leverage.
sroerick | 15 hours ago
sroerick | 15 hours ago
The dynamic features you get with a full blown REPL are, in some specific cases, worth the trade off you get by losing the guardrails (which I call Rubber Baby Buggy Bumpers).
You're right though - strong typing feels like a cheat code.
unprovable | 15 hours ago
copperx | 15 hours ago
> C++
I'm a bit confused here.
Jach | 14 hours ago
pseudony | 15 hours ago
Last I attempted to write some smaller ocaml project I used LLMs for support (but wrote myself). They generally weren’t excellent.
Honestly, types don’t help as much as people want them to. The LLM does best on popular languages, especially those whose use and feel is also mainstream.
That is to say, pick a niche language like Odin, and it may incorrectly start to over-apply Go’isms - knowledge from one language bleeds into how it approaches others.
brabel | 15 hours ago
Also if you really want Haskell types you can use Coalton, which is essentially Common Lisp with Haskell types, but lets you interop with Common Lisp seamlessly as Kotlin with Java.
pulkitbanta | 16 hours ago
I still believe that other languages which are understood by developers are and will be required and LLMs are trained on the same dataset so it can write the code.
vincnetas | 16 hours ago
Unless of course there are multiple dimensions of "Good". I in my opinion this is exactly the case. There are best languages per dimension, but not absolutely best.
bambax | 16 hours ago
rf15 | 16 hours ago
asp_hornet | 15 hours ago
pseudony | 15 hours ago
tgv | 16 hours ago
n4r9 | 15 hours ago
Someone | 15 hours ago
“a partial order on a set is an arrangement such that, for certain pairs of elements, one precedes the other. The word partial is used to indicate that not every pair of elements needs to be comparable; that is, there may be pairs for which neither element precedes the other”
GuestFAUniverse | 16 hours ago
Sorry, I'm not impressed. How did that /run/ to the top of HN? [pun intended]
"Vorschusslorbeeren"? A clique of voters?
Boring.
Revanche1367 | 15 hours ago
clx75 | 16 hours ago
What I like most is how fast testing goes: it modifies test code on disk, asks clj-reload to reload all affected namespaces, then reruns the tests. It's also fun to see how it verifies its assumptions by evaluating short programs through the nREPL.
I asked it to organize the various subsystems inside my playground repo into Integrant systems and make it possible for me to say things like "restart the http subsystem".
It also understands shadow-cljs: I replicated the necessary parts of the shadow CLI tooling in Clojure; now I can compile, watch and serve any number of CLJS apps located in various namespaces from inside the same JVM. There is no need to touch the command line any more: I just instruct the agent to start a particular CLJS app and it's there.
miroljub | 16 hours ago
clx75 | 15 hours ago
https://github.com/cellux/pi-extensions/tree/master/extensio...
Note that this may not work for you standalone as it relies on my other agent-sandbox extension (which ensures all agent operations happen inside a sandbox container).
But point an LLM to its source code and it will extract the gist of it.
chartered_stack | 15 hours ago
https://github.com/DeadMeme5441/arrodes
It seems to be a bit more integrated than what you describe here because it's completely built around a Clojure REPL
ews | 12 hours ago
miki123211 | 14 hours ago
Surely iPython (what powers Jupyter notebooks among other things) + maybe pdb could do it? And agents are definitely familiar with how those work.
azeirah | 12 hours ago
For iPython that's totally possible because the model there is just some kind of dependency graph. Regular python programs, much more complex I'd bet unless the architecture takes it into account
disgruntledphd2 | 12 hours ago
The iPython shell has %autoreload 2 (autoreload on changes) which is incredibly helpful for debugging and iteration.
I do agree that Lisp is better, hooking an agent up to Emacs is a lot of fun (and I'm only getting started!).
jwr | 13 hours ago
rcarmo | 9 hours ago
icey | 6 hours ago
I don't let the LLM write macros though, it creates too much opacity for me to easily reason about what they've done.
dexterlagan | 16 hours ago
cookiengineer | 15 hours ago
LLMs need a language that:
- has opinions about how to write standard code
- has opinions about one way of formatting and codestyle
- has opinions about linting and integrated debugging
- has predictable strong types
- has opinions about integrated unit testing and a standard way to write them
- has an integrated toolchain
- has a strong stdlib and an upstreamed way to unify libraries
- (recommended) has a standard project layout _where_ to put its code (types, structs, helpers, utils, etc)
Go and Rust fit all these checkmarks except the last one. That's why those languages don't need kilometer long prompts to tell the LLM how to write code. Most of the prompting in those languages focuses around architecture and design, and not about style, tooling, or other artificially vague decisions.
Lisp is the most unopinionated language there is, therefore it is the worst in terms of lack of decisions encoded in its tooling.
And I'm not writing that as an opponent of the language, I've written scheme bindings for a couple of years in (academic) robotics.
The point that I am making here is that you _need_ an opinionated language for an LLM to make sense. Write linters and tools before code [1].
[1] https://cookie.engineer/weblog/articles/write-linters-and-to...
rramadass | 12 hours ago
Have you looked at adding Formal Specification/Verification to the above checklist?
reikonomusha | 5 hours ago
Lisp isn't some language wild west lacking style and idioms—regardless of whether there's a "go fmt" for it or not. Most open source Lisp code is quite standard and "boring": functions and classes/structs following canonical indentation.
librasteve | 15 hours ago
Until then, I subscribe to the view that strong types fit LLM coding well since LLMs are prone to make silly mistakes when they patch together code examples in dynamic languages.
[Disclosure, my new language https://bil-lang.org aims to add “strong typing” around parallel programming to help weed out deadlock conditions.]
kelnos | 15 hours ago
Javascript/Python is the best because there's so much code out there that the LLMs can train on, and LLMs can write lots of tests to make sure everything is correct. There's nothing to compile, so the LLM can iterate quickly.
Rust is the best because the LLM gets great feedback from the compiler because of its strong type system, and it can deal with the borrow checker for you.
Go is the best because it's a simpler language with a decent type system, and the LLM can reason about that well, and will never forget to check an `err` return. The compiler is fast, so the LLM can iterate quickly.
I could write similar praise for C, C++, Java...
At this point I don't think any language is the best "because LLMs". I think there are quite a few languages that LLMs are probably not good at, but you have lots of choices if you want something they are good at.
bjoli | 15 hours ago
I mean, it can still reason about it, but the code it (Claude and Gemini) frequently doesn't compile and it throws edits onto it until it does. When it then compiles this pretty much c# written in a functional HM-typed sexpr language that lacks classes.
AI has been great help in writing the compiler, though. I got stuck in codegen after having written a lexer, parser and type checker, and not only did it make a faster and better code generator than I ever could, it also made the type checker about 5x fater.
Autious | 9 hours ago
varjag | 14 hours ago
mpweiher | 14 hours ago
It also allows for coding at a higher level, meaning that code is closer to the prompts.
Is it actually true? OF COURSE IT IS!!!
So no idea, but after long dismissing that idea precisely because it looks like a "just so" explanation for my obvious favorite choice I am starting to come around to the idea that it might just be true in spite of the obvious bias...and definitely worth testing.
https://objective.st
vconnor | 12 hours ago
Silly example. Instead of:
You could just do: Smalltalk is introspective enough to make this integration a breeze, remains to be seen how prone to hallucinations this might end up being.EDIT: fix the wonky syntax
resonious | 14 hours ago
Fight me. As a classical software engineer, I hate Go, but in this new era it wins so easily. For web, at least. The subjective stuff about how the language feels is all out the window now.
luipugs | 13 hours ago
toolslive | 12 hours ago
bigfishrunning | 10 hours ago
...
...It's not that different
kjs3 | 9 hours ago
bigfishrunning | 8 hours ago
toolslive | 9 hours ago
binary132 | 9 hours ago
mike_hearn | 13 hours ago
There's no good way to resolve these kinds of debates. I don't think AI changes much. It thinks in the same sorts of ways we do, just faster, so things humans find hard or unproductive can also be hard and unproductive for AI too. There are a few exceptions where it's able to reason fast enough that things which would be dumb for humans (like reading raw assembly or bytecode) are no big deal for AI. But mostly it's the same.
aktau | 12 hours ago
The parent did mention:
> ...and is simple to deploy.
Also, my experience with Java programs is that the memory overhead is even higher than Go (where it's a ~2x of what the program holds as heap due to the GOGC=100 default).
mike_hearn | 11 hours ago
But in reality most software isn't deployed by copying binaries around. You'd want it to be at minimum run by systemd or kubernetes, for example. And if you want a Docker container then it's pretty easy. Your framework probably configures it out of the box:
Push to the host and start it up.The reason it's not harder in the end is that the above takes cares of many annoying details that crop up in real deployment, like knowing what CPU and CPU extensions does the host have? Can you deploy incrementally without recopying the whole thing or does that not matter?
The above is for servers, but it's not really harder for CLI tools either. Fat JARs exist. If the user doesn't have a JVM, once again, they can install one easily from their package manager and then it's done - no need to create and distribute half a dozen binaries for all the different OS and CPU combinations that are out there.
But if you want to make AOT compiled binaries and get lower memory usage too, there is GraalVM which can do both. You've got the choice.
Note how the above debate isn't changed by AI in any way. Their weaknesses remain weaknesses, their strengths remain strengths. I wouldn't personally use Go because of its poor feature set and debugging support (errors don't reliably create stack traces). But the arrival of LLMs changes nothing about these preferences and choices. At most you can talk about token efficiency, in theory, but the attempts to measure the real world impact of that don't seem to have yielded decisive evidence.
erichocean | 10 hours ago
And compared to Go, the production observability is very good.
I also use Clojure and can observe my running app with a full REPL.
aktau | an hour ago
Can you give some examples where Go is lacking and Java is great? (Ideally builtin things, and if not builtin, then things where Go doesn't have an externally developed alternative).
> I also use Clojure and can observe my running app with a full REPL.
Does this work with other JVM languages, like Java, too?
ModernMech | 11 hours ago
This debate in particular is easy to resolve: there is no “best“ language for all use cases and I agree LLMs have not changed that. The best language for a scenario depends entirely on the scenario, so talking about best languages without a scenario in mind is completely the wrong discussion.
AnimalMuppet | 8 hours ago
I've never written a general program in my life. I've written a bunch of specific ones, though. What I care about is which language is best for writing this specific program. Why do I care about which language is best at writing a program that I'm not trying to write?
latexr | 12 hours ago
No. I’m sick of flame wars, and I already was before LLMs turned everyone even more insufferable. You can fight yourself in your own corner, if you like. Never thought I’d miss emacs VS vim.
Use whatever language you want, I couldn’t give less of a shit. I have no desire to waste time on a dick-measuring competition, and that’s doubly true because people in these fights are measuring other people’s dicks.
> As a classical software engineer, (…) The subjective stuff about how the language feels is all out the window now.
Subjective stuff never mattered to people who take no pride in doing proper work. That hasn’t changed because of LLMs, it only shone a brighter light on those people.
eric_cc | 7 hours ago
latexr | 4 hours ago
I do agree the comment came off strong and for that I apologise to the person above. I meant to make a general criticism, not condemn any particular individual.
jjav | 12 hours ago
dzogchen | 12 hours ago
robmccoll | 11 hours ago
loglog | 10 hours ago
binary132 | 9 hours ago
wffurr | 4 hours ago
pjmlp | 10 hours ago
Even better don't throw away the frameworks, learn to use JIT caches and hot code reloading tools, and you can even debug, edit and continue from the comfort of an IDE, doing several useful actions.
atilaneves | 12 hours ago
I think Go's compile times are great for the AI age, but that "pretty fast runtime" is not.
pjc50 | 11 hours ago
Go is close enough on runtime for all reasonable purposes.
zaphirplane | 9 hours ago
edoceo | 8 hours ago
infamouscow | 6 hours ago
ndr | 11 hours ago
AI + smaller docker images made me look at it again.
The fact I can test/build concurrently and much faster than rust, off the same repo without messing with sccache and worktrees, make it easily the winner for many web/self-contained scenarios.
yturijea | 10 hours ago
As a quite well seasoned software engineer working many years with Java, C#, PHP etc. both in complex calculation systems and webservices, go-lang just feels superior in so many aspects. While I might still fare better in say Java is purely a function of me spending much more time in it.
So I see your point in myself and agree and agree. (I just don't hate go, I think we need to give it a chance)
setopt | 9 hours ago
YuechenLi | 6 hours ago
You also get very easy parallelism in Goroutines, excellent ecosystem, and one of the best performance profilers in any programming language, pprof.
setopt | 5 hours ago
I’ve done pretty low-level C++ and Fortran in the past, but these days I’m honestly mostly used Python with either NumPy or CuPy for calculations, so not very low-level at all. My code is pretty performance-sensitive though, so unless the main operations can be framed neatly in terms of NumPy or CuPy primitives, one had to drop down to low level.
Do you know how the Go library support is for typical scientific computing stuff, e.g. matrix diagonalization, sparse matrices, or handing off such calculations to CUDA (or other GPU frameworks)? Is there a strong numerics community in Go these days, or would you have to implement most of what you need yourself?
YuechenLi | an hour ago
https://github.com/gonum/gonum
For sparse, I think the best is still Intel's sparse libraries for MKL on CPU, it's just not a workload that runs well on GPU compared to dense SGEMM. You can call it via CGo.
For GPU offloading, there should be a decent amount of CUDA bindings available for Go right now, but I'm not as familiar in that front since I'm building my own GPU compute runtime in Vulkan, but it's still very much an experimental work in progress right now.
donatj | 8 hours ago
miladyincontrol | 8 hours ago
ninininino | an hour ago
mwpmaybe | 13 hours ago
Is one I've heard...
pjc50 | 11 hours ago
dan-robertson | 11 hours ago
victorbjorklund | 13 hours ago
firemelt | 9 hours ago
flir | 12 hours ago
(I like frameworks and libraries for LLM work. They keep the machine on the rails for longer, and the end product is more consistent).
caaqil | 12 hours ago
I thought Rust was the best because it was moral.
misja111 | 8 hours ago
My experience with Python is different. I noticed that my LLM (Claude) frequently gets stuck in cycles when coding larger features in Python. I'm also doing a lot of work in Scala, a language where much less code is on the Internet, yet Claude does way better, it hardly ever gets stuck at all.
My theory is, that while there is a lot of Python code on the Internet, a lot of that code is crap. This will trap LLM's into coding crappy solutions which will eventually bite them in the tail.
Also, I believe that, especially for larger codebases, a strong type system helps not just humans but LLM's as well.
nilamo | 8 hours ago
Jtsummers | 6 hours ago
ninininino | an hour ago
artpar | 15 hours ago
I don't understand how people have that stated as a fact. Coding was never the slow part. Neither pre-llm, nor now. How it needs to be done is software engineering. And that corresponds now to the "thinking" part of llms, so unless you are making the llm "think in lisp" its not useful. How would that even work. training data to be completely in lisp ?
> Lisp programs are often much more concise because macros let you abstract away recurring patterns and make them part of the language itself
functions ?
> So the bigger the program gets, the bigger the difference. In my own experience the apps I've built in Common Lisp end up about six to seven times shorter than the Python versions
By that logic writing code in this concept language made up completely of symbols would take you even further ( https://github.com/artpar/guage ) but in practice it doesnt because llms arent trained to that extent on this.
atilaneves | 11 hours ago
Because, for them (and me!), it was. I never understood why people kept saying that typing speed wasn't a problem since coding was never the bottleneck. It always was for me.
> functions ?
Macros can condense code down a lot more since you're essentially writing a language within the language.
> but in practice it doesnt because llms arent trained to that extent on this.
Right, but it probably works with words instead of symbols.
skydhash | 11 hours ago
At least for me, typing code (in a language I know) is as easy as typing English (which is not my native language, but something I’m reasonably fluent in). Thinking in terms of generic computing concepts is equally as easy. Most of my time is spent on not contradicting what has been written before, not on what I need to express. Because the software needs to be coherent.
So the speed of coding is slow, because of all the double checks I need to do. Not because I don’t know which statements to introduce next. And that thinking happens at a meta level (state and its alterations) not at the syntax level.
rrook | 8 hours ago
This is the trapdoor in this discussion. The degree to which a concept has been thought of in terms of generic computing (or just generically; modeled abstractly), before code is written, is different for every programmer. Not only is the degree different, but the deliberation and levels of awareness vary as well.
If your mental process explicitly models in the abstract, translating and typing out to code can absolutely feel like a bottleneck, and the specific language involved matters.
skydhash | 7 hours ago
Unless you're talking about raw typing speed or using an unfamiliar language, I don't think that it is. Most languages have few tokens and syntax rules. The next layer is the symbols from the standard library and the dependencies. After came the conceptual models, which is where the abstract thinking happens (like how does an hashmap works or what writing to a file entails).
Speed at the level of the first two layers can be greatly improved by very basic completion (syntax and symbols), or by just copy pasting. Integration like vim's quickfix or emacs' compilation mode helps because that's where compilation errors happens.
My opinion (anecdotally verified) is that people that feel like coding is a bottleneck have no editor fluency.
rrook | 4 hours ago
My anecdata is the opposite of yours; the people that feel like the _actually coding_ is the bottleneck tend to be the people that have the _most_ editor fluency, as they are(were?) motivated to remove the bottleneck.
skydhash | 3 hours ago
I don't think so unless you're talking about raw assembly. Even with C, you got structs (clump of data) and functions (which give us nice abstractions over pieces of logi) as well as syntactic sugar for branching and looping. OOP is a whole different model of design. And FP has a whole other basis of computation theory (evaluation and reduction from lambda calculus).
It's kinda like drawing in a sense. People think they know what something is and you ask them to draw a chair or a bike and they can't do it. So you ask someone to give you the specs of something like an attendance form, and they can't readily give it. Drafting the specs is the real cost, not implementing it.
> My anecdata is the opposite of yours; the people that feel like the _actually coding_ is the bottleneck tend to be the people that have the _most_ editor fluency, as they are(were?) motivated to remove the bottleneck.
If it were, we wouldn't have a drought of editor models. Vim and Emacs are decades old. Then VSCode is basically the same thing as sublime, kate, notepad++, and the various IDE out there. Coding isn't the bottleneck.
FrustratedMonky | 8 hours ago
Some people do see typing English as a bottle neck, because they have so many thoughts running in their head so fast, they can't get them out on paper fast enough. The Typing is a bottle neck.
I've had that experience with programming also, where I know exactly what I want, and typing it all in takes extra time.
le-mark | 11 hours ago
This is what I’ve always maintained as well. More honestly I think there were two broad cases. First when the task is “copy this feature” coding can be clearly slower. Example when marketing says add a wishlist to e-commerce site. And when asked for requirements they say “just copy competitor.com”.
The other is adding features that require real decisions from people up the chain. A lot of us have worked on simple projects that should have taken a month that stretched on many months, sometimes to even being cancelled with nothing shipped. These are the one llms won’t help with.
layer8 | 10 hours ago
[0] which I only rarely encountered in practice — maybe because I never had to do marketing-driven work
stevoski | 15 hours ago
He claimed that Common Lisp was the best programming language for web apps, and that it gave him a massive advantage in creating his app.
What was his name again? Paul something? Oh yeah, Paul Graham.
pfdietz | 8 hours ago
varjag | 15 hours ago
WalterBright | 15 hours ago
This is why Lisp never catches on. Each programmer invents their own ad-hoc, undocumented, barely working language in the form of those macros. The same thing happens in other languages with macros (like C and assembler).
stackghost | 15 hours ago
>[…]The same thing happens in other languages with macros (like C and assembler).
Uh, C definitely caught on.
colordrops | 15 hours ago
DonHopkins | 12 hours ago
pjmlp | 10 hours ago
WalterBright | an hour ago
I have seen endless incomprehensible preprocessor crap. People have even used it for metaprogramming.
I used to use it for metaprogramming, too. One day, I decided to rip it all out and use the preprocessor as little as possible. The result was much better.
Analogous to C are the macro processors for assemblers. A friend of mine who worked at Microsoft told me about an assembler program that was about 50k. The person who wrote it had left the company. There was a bug in the program, and the manager assigned it to programmer after programmer, and they'd all give up on it after a couple weeks. So my friend said he'd fix it, and fixed it in a couple hours and checked it in.
The manager was surprised. He asked how my friend managed it. My friend said that the code was all macros to create a magical special undocumented half-assed language that nobody could understand. So he ran the object file through a disassembler (mine) to generate source code. Then the bug was obvious, he fixed it, and checked in the disassembly as the new source.
I've also heard complaints from Scala programmers about the overuse of macros making for incomprehensible code.
Jach | 15 hours ago
WalterBright | an hour ago
That's the downfall I am referring to. The trouble happens when the creator of the nifty undocumented kludge language hands it off to someone else, who scraps it and re-implements it in Python or whatever.
I remember when C++ experts discovered "expression templates", which provided the ability to turn ordinary C++ code into a magical kludge language that did not at all behave like C++. It was all the rage for a year or two, then sank without a trace.
ludston | 14 hours ago
I say this as somebody that loves CL dearly. The difference between princ, prin1 and print; set, setf and setq; =, eq, eql, equal and equalp is more than most programmers can be bothered to memorize.
DonHopkins | 12 hours ago
Any sufficiently complicated Common Lisp program contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp.
https://en.wikipedia.org/wiki/Greenspun%27s_tenth_rule
WalterBright | an hour ago
DonHopkins | 10 minutes ago
I thought it was gross, but then I met Microsoft COM.
ball_of_lint | 15 hours ago
So long as your feedback loop isn't slow enough to be taking you out of flow, it's fast enough. Having it be a few seconds vs a few hundred milliseconds mostly doesn't matter for a human, and will matter even less for an LLM.
On macros and DSLs, yes they're cool and even useful sometimes, but most of the software industry is working quite happily without them. And LLMs aren't going to change that because they are best when there's a lot of relevant patterns in their training data. That ends up being a far more important factor in their effectiveness than whether the language itself is token-efficient. It's much easier for an LLM to reason through how to do XYZ in Python where it already knows all the semantics and syntax than it is for it to do it in your DSL it's never seen before. To be clear, it can probably do both but will make mistakes an order of magnitude more in the DSL, and that's what will matter most for the LLM iteration time.
long____cat | 15 hours ago
https://autolith.rocks
sroerick | 15 hours ago
magnusi | 13 hours ago
pka | 10 hours ago
peri-cl | 15 hours ago
(That's an impossible count (3) of parentheses on the RHS—like a six-fingered hand).
IsTom | 13 hours ago
layer8 | 10 hours ago
IsTom | 9 hours ago
layer8 | 4 hours ago
IsTom | 3 hours ago
[0] e.g. r_1 = (\(\))*, r_d+1 = (\(r_d*\))*
Zambyte | 9 hours ago
Assuming let isn't shadowed :)
Quitschquat | 7 hours ago
miohtama | 15 hours ago
If it were a good programming language people would be using it more at this point.
lynx97 | 12 hours ago
attila-lendvai | 12 hours ago
this applies even to programmers.
attila-lendvai | 12 hours ago
brainless | 15 hours ago
Again, not a language expert, but: if you can describe your domain or business logic as clearly as a strongly typed language with a rich type system, most of your issues are gone. Enum and Struct with all the other core types plus the match expressions does most of my mental heavy lifting. I do not even track any of the recent language changes.
What I really care about is the shape of what I am describing - does it translate to code? How much do I lose in the translation? I want to try other languages, particularly Lisp but Rust is at this moment my choice. I have my own UI framework (1), my own provenance based business domain generator and a few simple language parsers.
I am building apps where you can, for example, throw a CSV file (2), ask questions in English and get answers - without using an LLM. Parser. Rust is no doubt a great language to express - not as a programmer, but as a prompter. I do not write the code. I ask LLMs to generate it using only basic knowledge of Rust and its type system. This will be the key to work with LLMs for majority of people.
1. https://github.com/brainless/akar
2. https://github.com/brainless/baho
Revanche1367 | 15 hours ago
kjs3 | 9 hours ago
Revanche1367 | 5 hours ago
Hobrot | 15 hours ago
sidkshatriya | 15 hours ago
I'd rather my language surface problems at compile time (via type errors) for all possible code paths rather than a particular codepath at runtime.
That said, nothing prevents an LLM from controlling gdb/lldb either.
Also this is actually not such a big win: resuming the program after making the change aka hot reload is often a very hard problem even in highly dynamic languages like erlang and common lisp. What if the schema changes etc. ?
> To my knowledge Common Lisp is the only mainstream language that does all of this.
You should also mention racket, chicken scheme, guile scheme, MIT scheme, erlang/elixir, even Python via the repl and so on ...
> This is what makes macros possible. A macro is a function that takes your code and returns new code in its place, that means you can add new constructs to the language itself.
Macros + untyped code means the code cannot scale beyond a few thousand lines easily. Only a few programmers may understand the program fully and it’s often in their head rather than documented. Types implicitly document the code and allow hundreds of programmers to work on it. Lack of typing hinders code refactors too.
> Common Lisp is an ANSI standard and it hasn't been updated since 1994. I like this feature.
I like stable languages but not ossified languages. The internet was in its primitive infancy in 1994. This means Common Lisp may not be as web friendly as, say, golang without external libraries. Moreover, there has been a lot of progress in programming languages since 1994. OCaml/Haskell/Rust/Python/Ruby etc. incorporate some of that.
> But I don't think that's a problem anymore. Most programs today depend on millions of lines of code from packages that keep getting compromised. You don’t want that in yours. Also, with an LLM you could just write the part you need yourself or port the whole library — and LLMs seem to be really good at porting code.
You claimed earlier in the article that since Common Lisp was very concise you needed to spend fewer tokens via LLMs. But now that libraries for common tasks are not available you need to spend extra money on tokens to generate that functionality from scratch ! There goes your token budget !
Which is better ? A from-scratch LLM implementation of something with security holes or a library downloaded from the internet ? If you can restrict your dependencies to stable and popular packages from npm/cargo/pip you will probably be better off.
farhanhubble | 15 hours ago
kjs3 | 9 hours ago
Like your idea about SectorLISP, btw.
drwallace | 15 hours ago
fc417fc802 | 14 hours ago
reikonomusha | 12 hours ago
1. Common Lisp has a whole system for defining and declaring types, and implementations do actually use this information to produce safer and higher performing code, including some amount of static, compile-time verification (e.g., you can get "expected an INTEGER but got a STRING" styles of compile-time errors). Fast, competitive-with-C floating-point math is achieved this way. DEFTYPE/DECLARE work.
2. If you prefer a system that doesn't feel like a 1980s barebones type system (i.e., Common Lisp's), then you can use Coalton [1] which adds types to a Scheme-like DSL within Common Lisp, but has a type system like Haskell's (similar to stock GHC + common extensions). Multi-parameter type classes, monomorphization, functional dependencies, etc. Yet it's still fully interoperable with Lisp code, uses the same Lisp toolchain, same Lisp compilers, same Lisp editors, etc. so it really isn't just a language atop Lisp, but something that's integrated within it.
[1] https://coalton-lang.github.io/manual/
drwallace | 11 hours ago
fc417fc802 | 10 hours ago
reikonomusha | 5 hours ago
Jtsummers | 5 hours ago
rfgplk | 14 hours ago
keybored | 14 hours ago
Endless article generator.
Archit3ch | 14 hours ago
john_owl | 14 hours ago
p410n3 | 13 hours ago
mrlonglong | 14 hours ago
perarneng | 14 hours ago
peter_retief | 14 hours ago
cissikatt | 9 hours ago
fedeb95 | 14 hours ago
soltanov | 13 hours ago
lynx97 | 13 hours ago
p410n3 | 13 hours ago
MarceColl | 13 hours ago
nibbula | 12 hours ago
nibbula | 12 hours ago
but_the_aeropla | 11 hours ago
High level programming exists on a spectrum between C and Lisp. Your Go or Python is just a DSL.
zombot | 11 hours ago
It's so disappointing to see this repeated everywhere.
tzury | 11 hours ago
scotty79 | 10 hours ago
I almost never read the code now so how well I know the language is pretty irrelevant.
What's most important is how well the language can give feedback to LLM while developing.
Second most important factor is how good technically is the final artifact.
Rust seems to be quite good for both.
Speed of iteration would be cool but LLM thinking takes the bulk of time anyways.
Live debug through something other than computer use would be cool as well but I don't know how well LLMs can use auch things for any given language. I just usually tell it to heavily instrument code and put in debug bridges in the app its building to inspect and ivoke stuff while the app is running.
laerus | 10 hours ago
chrisjj | 10 hours ago
I'm amazed how slow they are.
I ask ChatGPT to change <title> on a 1000-line HTML file. It completes in 32s.
I ask for line count. It takes >20s. And the bot claims "1-2s".
dzonga | 10 hours ago
intrasight | 9 hours ago
sammy0910 | 9 hours ago
rcarmo | 9 hours ago
I end up using Go for mostly everything, though, because a) Python is too slow and b) Rust will regularly fill up the hard disk of _every single one of my sandboxes_ with just... too much stuff, and takes around four times as much to compile (I can build, link, profile and fuzz Go in the same time I get a plain Rust build).
Still, I hope that one day I will be able to do just LISP :)
Rochus | 9 hours ago
firefax | 9 hours ago
the superfans make me not want to explore it ;-)
vegnus | 8 hours ago
quixoticaxolotl | 8 hours ago
This is a bold assertion, and one I think undermines the merits of the argument.
"Change the product themselves" - I haven't seen any software company or discussion propose doing this. I'm not sure how it'll work. It sounds like it blurs the line between configuration (letting the user toggle certain classes of existing behaviour) and forking (the user now owns the contract with the product).
Perhaps the idea is something like an embedded scripting language as used in some games, where users can write custom snippets to glue together what they want. Or a relational language to generate custom visualizations from raw data. These are the only forerunners of the concept that have found actual market fit to date that I'm aware of.
It's hard to generalize where else this would be useful outside of these domains, and in any domain I can think of - UI interfaces, data analysis, procedural generation - there is already a dominant language runtime bound to the environment (Javascript in browsers, C# in Unity) exposed to customers. Would Common Lisp be able to displace them? If so, why and how would it improve?
Merits on the basis of the runtime itself are meaningful, but the runtime needs to be popular for the value of the PL to be seen.
dingaling911 | 8 hours ago
FrustratedMonky | 8 hours ago
But end users are using LLMs to write their own code.
It's more like the new Excel. End users used to get a data dump from SAP,ERP or something, and use Excel to manipulate. Now they use the LLM, and are also using LLM to build large cobbled together messes.
Excel is the bane of accuracy, any miss aligned row in a calc and suddenly a company is loosing billions. LLM's are this on steroids.
otikik | 8 hours ago
maxiepoo | 8 hours ago
pfdietz | 8 hours ago
Jtsummers | 8 hours ago
davexunit | 8 hours ago
https://github.com/vivienhenz24/vivien/commits?author=vivien...
fancythat | 3 hours ago
Kevcmk | 8 hours ago
moishe_szyslak | 8 hours ago
labrador | 7 hours ago
arrakeen | an hour ago