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?
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.
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.
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 even that is just a superstitious anachronism.
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 just 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.
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.
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.
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.
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 years, but 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.
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!
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 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 flee 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"
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.
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.
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.
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.
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.
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?
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.
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.
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.
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).
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.
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.
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.
... 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.
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.
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.
> 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 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.
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?
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.
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 to completely suck at Arc (the Lisp that HN is written in). But it's not so.
They excel at Common Lisp—which admittedly has decades of history and documentation which the models have all slurped up. But they're even good at Arc, which is rather far down the long tail. And when they mess up, I put something in agents.md and that issue mostly just goes away.
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 just 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.
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.
- 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...
False. A partial ordering doesn’t guarantee a maximum element!
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 years, but 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.
I dont really understand what macros get you when llms exist since a llm doesnt really need to create dsls to get work done
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).
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 flee than elephant to grizzly bear. The smaller scale means there's less "overhead" code, which means you can go smaller still.
I agree with your point
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.
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.
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.
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.
https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
But I find ideas like Carp very attractive.
Still common lisp is designed very good.
Partly, having to do my own pentesting and red team work instead of using someone else's work that has already been pentested.
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.
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.
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.
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).
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.
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.
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.
DSL presumes agreement on semantics, and that's often the most difficult part.
Astronaut 2: Always has been...
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.
> 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.
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.
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?
https://news.ycombinator.com/item?id=49974251
(not a CL expert here)
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.
They excel at Common Lisp—which admittedly has decades of history and documentation which the models have all slurped up. But they're even good at Arc, which is rather far down the long tail. And when they mess up, I put something in agents.md and that issue mostly just goes away.