HI version is available. Content is displayed in original English for accuracy.
Advertisement
Advertisement
⚡ Community Insights
Discussion Sentiment
76% Positive
Analyzed from 2735 words in the discussion.
Trending Topics
#lisp#code#agent#more#languages#eval#javascript#data#write#llm

Discussion (54 Comments)Read Original on HackerNews
Homoiconicity, as I understand, is that the code is structured data that is easy to programmatically modify, hence allowing Lisp macros. While some might disagree, I see Rust macros as the closest thing that demonstrates homoiconicity in mainstream Algol-based languages, as Rust macros modify the loosely structured token stream to produce new Rust code.
Eval, on the other hand, that’s more of a capability that comes from Lisp’s runtime, which used to be unique when Lisp was thriving, but not anymore — JS, Python, Ruby, all of the runtime-based languages have an eval function. The fact that they are not used as much is more of a security issue, not a capability issue, and I am not sure how having eval can be argued as Lisp being the language of agents.
Because Lisp syntax is so much simpler than that of JavaScript etc. it is much easier to avoid errors when generating code. In JavaScript you can use JSON to generate data, but JSON can not carry functions around.
I think this idea makes a lot sense. Instead of making the LLM generate JSON or XML, why not make it generate Lisp, which can carry both programs and data?
But maybe I’m interpreting you too narrowly?
> Because Lisp syntax is so much simpler than that of JavaScript etc. it is much easier to avoid errors when generating code. In JavaScript you can use JSON to generate data, but JSON can not carry functions around.
First of all, the LLM does not produce structured tokens, it produces (tokens of) text. It does not have a concept of nested or structured tokens. Which means that producing a Lisp program and a JavaScript program is basically the same difficulty, i.e. LLMs producing function foo () {} is about the same task as producing (defun foo () ()).
In fact, because most Lisps uses the same token ( and ) for almost all delimiters, the LLM in fact gets confused a lot more than Algol-family languages that uses various different tokens for different purposes. It generates thinking traces that are a few screens long while trying to count the closing parenthesis and the depth, something that I have not found to be the case for other languages, even with languages much more obscure than Lisp. (And no, it is not a training data issue, because the Lisp family as a whole is pretty well represented in the data set, due to Emacs Lisp.)
> I think this idea makes a lot sense. Instead of making the LLM generate JSON or XML, why not make it generate Lisp, which can carry both programs and data?
You do realize that all programming languages contains both programs and data, right? i.e. JSON is literally a subset of the JavaScript language, all JSON documents are valid JavaScript code, can be embedded in JavaScript programs, and so on. This isn't even a JavaScript-specific thing, almost all recent programming languages have data structure literals.
The thing that makes Lisp unique is that it can modify programs as data in the language easily, and why would that be a unique capability that would be beneficial for LLMs, when it can just sed/awk or tree-sitter-parse programs with more conventional languages and modify it as easily?
Yet I still had the idea that LLMs should be better at lisp than other languages.
A fascinating contradiction, thanks for pointing this out.
I don't know if you consider Elixir mainstream, but IMO their macro system is much closer to lisp's ideal.
Elixir is basically Lisp, but with better syntax, a modern ecosystem, and running on the Beam. Unlike languages like Rust, Elixir's conditionals and function definitions are just calls in the AST, even though the syntax looks mainstream and not like paren soup.
Which so helpfully removes many of the benefits of lisps :P I don't understand this argument at all, if it's not s-expressions nor quacks like a lisp, in what way is it lisp at all? Making it algol/C-like already makes it like the rest of the 99% of the languages, without any of the easy benefits of the neat and simple syntax of lisps.
And it's good we have those for troubleshooting but those eval still offer nowhere near the power of a Lisp REPL.
> and I am not sure how having eval can be argued as Lisp being the language of agents
I've seen several Lisp programmers saying that it's really the REPL (and the 'E' in REPL is for "Eval") that is the godsend when working with LLMs.
With LLMs we've seen terminals/ssh/tmux and CLI tools calling making a huge comeback (not that they ever went very far).
Now I wouldn't be surprised if we were soon to see a Lisp AI harness also using extensively the REPL becoming succesful.
It's too early to tell it's not a powerful combo (LLMs + Lisp REPL).
I think a more accurate description is that lisp code is just cons cells and cons cells is both how we write the code and how the runtime itself implements lists. So there’s basically no distinction between a lisp list and the text representation of the source. Rust and its macros is a different situation because the text representation of the code and the syntax tree have completely different shapes
Heresy I suppose, but doesn't feel (to me) like it "specifically" has to be cons cells, as long as it's a "list" of some sort, regardless of the specific type of list, while I'd say "cons cells" is a specific implementation of a list, famously implemented by Common Lisp and similar lisps.
Besides that I agree with you, the code you write and the code the compiler uses is the same, that's why it gets easy to edit with code itself. Rust and similar don't have this feature at all, it's very different experience writing macros with lots of special syntax, compared to just writing "normal" code doing the same sort of operations. "Doing a loop of X" isn't the same inside of a normal runtime function body, as when you want to do a loop of arguments for the function signature in a macro, as just one simple example.
Not to mention in Rust you have two different types of macros, declarative vs procedural macros, already speaking to that the entire concept of homoiconicity isn't there. You want it to be easy to implement a read, evaluate, print and loop back workflow by passing native data through the entire chain, except parsing the initial user-input string into your AST (whatever shape that ends up being), which pretty-printed (basically) looks the same as the user-input.
Yeah, "cons" is definitely a implementation detail. Maybe the central ideia is just "parenthesized s-expr".
There is a case to be made for a dynamically evolving "tool server", but it should be a separate process. That would be more flexible for other use cases too. For example, multiple independent agent processes could talk to one shared tool server. Like a blackboard system, more classic AI!
And if you really do want to evolve the agent itself: As the article observes, its entire state can be serialized. Nothing is gained from hanging on to one particular agent process. Serialize its state, ask the tool server to kill it, rewrite its code, then start the new version and replay the state.
So cool to watch the AI get into a tight learning loop when it has access to all the internal data structures.
Not sure what you mean. The system I outlined is one where some "state" resides outside the process in a separate server. You don't need to serialize that, you just need to serialize the information to need to reconnect.
And my first point is even more relevant the more complex/distributed/brittle you make the whole thing: The more important it is for some specific process to stay alive no matter what, the less you want to live-slop code into it.
(Edit: Yes I'm aware of the live-patched space probe story. Human live-patching is not the same as letting an LLM try to one-shot the correct patch.)
It is always nice to appreciate how much power you get out of (Model + the absolute bare minimum of control flow). There is just so much baked into the models now that given an inch they will take a mile.
In contrast, `eval` runs the code in the same execution context as the agent loop. When `eval` finishes, that execution context still exists. For example, any functions defined during an `eval` call remain available for later use.
Being able to run code in the same unix process or a new one doesn’t really matter all that much in the context of self modifying code. But even if we cared about that, this isn’t a LISP specific feature. All dynamic languages support eval.
And having the agent cache the tool for reuse is a really trivial problem to solve. Though I do agree that LISP makes this much easier than in many other languages.
This is certainly a cool tech demo. But the claims of its novelty are overstated
Yeah, it's a small example (it's in the title, "100 lines") so obviously doesn't highlight the best benefits once you reach larger codebase size.
Still think ~8 lines for the core loop is probably more elegant, readable and concise than you can achieve in other algol/C-like languages, but happy to be shown that I'm wrong :)
The part that killed it for me was losing everything if the lisp crashed (sonnet 3.5 was prone to doing that) and solving persistence had too many edge cases and confused the model.
Later realized that writing the agent as 20 lines of bash was equivalently powerful to the lisp agent, but made persistence trivial from the easy file system interop.
you get a snippet from LLM, compile it to module, and hot-load it into the running node. the module lives in the node's code table, so it persists and every other agent can call it. not just the one that wrote it.
the agents themselves are seaprate supervised processes, so if one crashes - e.g. because the snippet was crap, it doesn't take down whole system.
of course you can do that in just elixir too, the lisp is just cosmetics really.
It's pretty amazing to write your own agent BTW. I've got a zero-dependency all-in-one-file agent harness I wrote myself. I use it all the time now because I can get it from anywhere and I can know EXACTLY what it'll do (as much as you can with any model), what it's been told vs not. Using it as a harness for models I'm hosting myself makes me feel like some kind of LLM homesteader: it's a set of tools I'll always have that will only change as much as I want it to change.
Most of those "permission"-systems are built on the idea that an LLM can decide what to ask for approval to run or not anyways, which obviously don't work out great in practice. Might as well give them blanket permission to do whatever, then put them in an isolated environment.
What does it contribute? I can read and discern this for myself, I can then stop reading or decide I don’t care.
Seriously, at some point all you “ai writing sleuths” should just get your own discussion thread together. It’s been months of this , we get it already.
(Not directly just at you, but anyone who feels the need to drop these comments in every thread)
Indeed. The new language, llmish, has definitely been used.
Still a good TFA IMO.
P.S: this particular construct "No X. No Y. ... just ..." is half my LinkedIn feed.