It seems like this is more like writing Rust in an s-expression syntax instead of having a proper lisp dialect that compiles to Rust, which is cool I guess but not very interesting.
It's quite weird-looking for someone who's done any amount of lisp programming.
I think some comments are missing the upside of it being precisely Rust, without any new semantics. If you want lisp that compiles to machine code, Common Lisp can get reasonably efficient. The purpose of bringing Rust into it is to surface Rust-specific semantics -- which many people quite like!
Unfortunately, given the clear LLM basis of this project, s-expressions aren't a great choice. I've found coding agents struggle really hard with s-expression parentheses matching.
Much better to give them something more M-expr styled, I think a grammar that is LL(1) is probably helpful in that regard.
Basically the more you can piggyback on the training data depth for algol-style and pythonic languages the better.
It is absolutely not true, I've vibecoded an app for myself in CL and opus/sonnet had 0 problems with parens and types. Add to it an MCP to work with REPL and it is much more smooth than Go in my experience.
I like the ubiquitous type inference. It reminds me a bit of ELSA for Emacs Lisp: https://github.com/emacs-elsa/Elsa. In particular, type aware macros have been on my wishlist forever: there's no good reason I shouldn't be able to write, e.g. an elisp or CL/SBCL compiler-macro that specializes an operation based on its inferred type. In normal lisps, it's hard to get even the declared types.
That said, I wish that part of Loon were less coupled to the allocation model though. What made you opt for mandatory manual memory management in an otherwise high-level language? And effects?
There are two things common in language design that, honestly, strike me as unnecessary:
1. manual allocation and lifetime stacking, and
2. algebraic effects.
On 1: I think we often conflate the benefits of Rust-style mutability-xor-aliased reference discipline with the benefits of using literal malloc and free. You can achieve the former without necessitating the latter, and I think it leads to a nicer language experience.
It's not just true that GC "comes with latency spikes, higher memory usage, and unpredictable pauses" in any meaningful way with modern implementations of the concept. If anything, it leads to more consistent latency (no synchronous Drop of huge trees at unpredictable times) and better memory use (because good GCs use compressed pointers and compaction).
On 2: I get non-algebraic effects for delimited continuations. But lately I've seen people using non-flow-magical effects for everything. If you need to talk to a database, pick a database interface and pass an object implementing the interface to the code that needs it. Effects do basically the same thing, but implicitly.
How do you change the syntax to eliminate reverse compatibility? I guess you could change the names of most key functions between releases. But to be compatible with rust you would need to make breaking changes every release.
So if I wanted to actually use this and I write some rust-but-lisp code and there's a compile error, will it show me a nice error message with an arrow pointing to where the error happened in my lisp code?
Can I use the amazing `rust-analyzer` LSP to get cool IDE features?
I suspect the answer is no, but these might be good further prompts to use.
For everyone who is shaming on the project for "not implementing enough," then you can definitely help me with it.
For everyone who is shaming on the project for being "LLM slop," sure but that's the reason why something like this can exist in the first place. The point isn't to be a finished, production-ready product. The point is to be an interesting work, and just a sly bit silly
Pretty nifty. As of now, the code doesn't compile: there's some stray "span" stuff in codegen.rs[1], and it's trying to format `Warning` which doesn't implement `Display` in main.rs[2].
Fixing these, it runs mostly as advertised, but it seems to assume that one-letter types are always generic parameters, so it's impossible to (for example) generate this:
struct X;
enum A {
P(X),
Q
}
Trying this:
(struct X)
(enum A (P X) Q)
produces this:
struct X;
enum A<P, X> { Q }
while using a multi-letter type like `String`:
(enum A (P String) Q)
produces the expected:
enum A { P(String), Q }
One way to solve this would be to always require the generic annotation, and let it be empty when there are no generics, but when I tried that it did something weird:
Reminds me of when I brainstormed an S-expression-based alternative syntax for Rust in the comments on an article about Rust's syntax: <https://news.ycombinator.com/item?id=41399712>.
I think this misses the point of LISP macros. LISP macros are just functions written in LISP, so here macros need to be functions written in Rust-but-LISP, but it is not so. In fact, I think this macro language lacks conditionals, so it’s not even Turing-complete.
37 comments
[ 2.5 ms ] story [ 59.1 ms ] threadIt's quite weird-looking for someone who's done any amount of lisp programming.
https://fennel-lang.org/
It reads as No X no Y just slop to me every time.
Much better to give them something more M-expr styled, I think a grammar that is LL(1) is probably helpful in that regard.
Basically the more you can piggyback on the training data depth for algol-style and pythonic languages the better.
That was basically my intent with this project, but I took the laziest way to get there lol
That said, I wish that part of Loon were less coupled to the allocation model though. What made you opt for mandatory manual memory management in an otherwise high-level language? And effects?
There are two things common in language design that, honestly, strike me as unnecessary:
1. manual allocation and lifetime stacking, and
2. algebraic effects.
On 1: I think we often conflate the benefits of Rust-style mutability-xor-aliased reference discipline with the benefits of using literal malloc and free. You can achieve the former without necessitating the latter, and I think it leads to a nicer language experience.
It's not just true that GC "comes with latency spikes, higher memory usage, and unpredictable pauses" in any meaningful way with modern implementations of the concept. If anything, it leads to more consistent latency (no synchronous Drop of huge trees at unpredictable times) and better memory use (because good GCs use compressed pointers and compaction).
On 2: I get non-algebraic effects for delimited continuations. But lately I've seen people using non-flow-magical effects for everything. If you need to talk to a database, pick a database interface and pass an object implementing the interface to the code that needs it. Effects do basically the same thing, but implicitly.
Carp is about 10 years old and has some cool demo's (like SDL for gamedev).
> The key features of Carp are the following:
> * Automatic and deterministic memory management (no garbage collector or VM)
> * Inferred static types for great speed and reliability
> * Ownership tracking enables a functional programming style while still using mutation of cache-friendly data structures under the hood
> * No hidden performance penalties – allocation and copying are explicit > * Straightforward integration with existing C code
> * Lisp macros, compile time scripting and a helpful REPL
[1]: https://github.com/carp-lang/Carp
I don't even feel bad saying this because clearly OP is just the front for Claude here.
Can I use the amazing `rust-analyzer` LSP to get cool IDE features?
I suspect the answer is no, but these might be good further prompts to use.
Can we please write our own READMEs before posting to HN?
Any sufficiently complicated C or Fortran program contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp.
Maybe we should one day include Golang or Rust to it
https://en.wikipedia.org/wiki/Greenspun%27s_tenth_rule
For everyone who is shaming on the project for being "LLM slop," sure but that's the reason why something like this can exist in the first place. The point isn't to be a finished, production-ready product. The point is to be an interesting work, and just a sly bit silly
some pre-processor that "compiles into rust" from less awful syntax?
Fixing these, it runs mostly as advertised, but it seems to assume that one-letter types are always generic parameters, so it's impossible to (for example) generate this:
Trying this: produces this: while using a multi-letter type like `String`: produces the expected: One way to solve this would be to always require the generic annotation, and let it be empty when there are no generics, but when I tried that it did something weird: produces: I have no idea where the `_` and the comment came from.[1] https://github.com/ThatXliner/rust-but-lisp/blob/70c51a107b2...
[2] https://github.com/ThatXliner/rust-but-lisp/blob/70c51a107b2...
I don't know what this is, but clearly not Lisp...
https://www.eriksvedang.com/carp
https://github.com/tomhrr/dale