For anyone who didn't know yet, from the file ReadMe.org:
> Note that while Cakelisp has "Lisp" in the name, Cakelisp takes some inspiration from Lisp, but is not compatible and does not aspire to become "a Lisp". I was inspired by Naughty Dog's use of GOAL, GOOL, and Racket/Scheme (on their modern titles). I've also taken several ideas from Jonathan Blow's talks on Jai.
Under list of (end-user) language features, the author says:
> I also think garbage collectors add more complexity than manual management
That's quite the controversial statement and flies in the face of what GC has enabled in software development generally over the last few decades.
I would be excited to understand some examples where this is the case and they outweigh the benefits that GC brings to developing software (incl. games).
I think one could make the argument that when you're producing high performance real time applications, diagnosing why your GC pauses and trying to find workarounds/fixes can take a lot longer than just managing the memory yourself. There's a lot of techniques like object pooling that people use in unity because .NETs GC isn't really built to handle how dynamic some of these applications end up being.
It's not a particularly controversial statement in the context of games. Many commercial games are still developed entirely or primarily in non garbage collected languages like C++.
Even Unity which uses garbage collected C# for game code is still a C++ engine under the hood for much of the performance critical engine code.
Having worked in games and VR development for many years I can say that memory management was not a major problem in C++ and for tricky areas of memory and resource management like GPU memory garbage collection doesn't generally help you.
When it comes time to optimize memory usage and performance it's often easier when memory management is explicit, deterministic and visible as well as highly customizable than when you have to work with a garbage collector which is a bit of a black box and has a lot of non deterministic and unpredictable behaviour.
Even after working for several years primarily in Unity with C# I still tend to prefer non garbage collected languages for games.
Seems to be vying for the same compile-to-C niche as Nim, though Lisp homoiconicity is welcome. I must say though, from the summary, I'm not a fan of using C type keywords. I much prefer the adoption of something unambiguous e.g. Rust or D. Nim prefers 64-bit numeric types but has compatible subtypes with bit widths in them (int32, float32).
Viewed as a lisp this is disappointing. Manual memory management means lexical closures will be a pain. Native integers mean you have to remember about overflow. Stuff that lisp doesn't care about.
Viewed as a replacement for the C preprocessor, or perhaps for C++ templates, this is very compelling. When you know the C you'd like to write, but it's annoyingly verbose and updating it by hand means changing a dozen places at once, systems like this amount to a more structured code generator.
It might be worth copying the sometimes-garbage-collected approach. In particular, you could have real garbage collection at compile time and raise an error if any of the residual code wants to call into it.
There is a module in gamelib [0] which will automatically clone, build, and link raylib to your program. There are several other third party libs there as well, including SDL2 and Curl.
It mentions no garbage collection, but there's surprisingly little explanation of what it does instead. There is mention of a "new" expression, but no matching "free". Did the author just punt on this?
Given the interoperability with C++ headers and templates, I'd expect that EVE or Highway should just work. I wonder if Cake macros could even express SPMD.
This looks cool (although i dont buy the popular claim that GC is a no no for game dev) i wonder how does this compare to past projects to create Common Lisp without GC. For example, ThinLisp - https://www.thinlisp.org/ - and if this project could learn something from past projects in order to not reinvent the wheel
GC is a no no for game development when the game is trying to extract every last cpu cycle and byte of ram, which is most leading edge games. If you’re not in that category GC is fine, but if you are it’s a massive tax.
I don't disagree with you, but it might help to be clear here. When people (including me) say "GC" they usually mean tracing GC. Technically, reference counting and other techniques are also GC. Except for short-running scripts (where memory consumption literally can't grow enough to matter) or the very hardest of hard real-time (where any kind of dynamic allocation is verboten), every program needs some way to find and recycle garbage. Games are not in either of those excepted categories.
I'm not just saying this to be pedantic. It's important to note that every GC method involves some overhead and has a potential to make execution time less deterministic. This is certainly true of reference counting, generally true of object pooling, etc. There are even cases where tracing GC improves locality of reference vs. other methods, and thus improves performance and predictability, though those are rare AFAIK. So when you say "extract every last CPU cycle" it's important that such a goal does not necessarily favor some other approach.
Many would also say that an aversion to tracing GC does not justify having nothing beyond plain malloc/free. No borrow checker, no reference counting (which can be done with very low overhead), no arena or custom-allocator support? Not in 2023. Leaving programmers entirely on their own for memory correctness is an eminently questionable choice, especially when other language features make gratuitous or hidden allocations easy. As a C programmer for 30 years I'm not going to say it's an illegal choice, but it certainly needs more than "but games" to justify it.
I have never seen a game developer argue for "no GC" in games, where "no GC" includes no arena allocators. anyone who advocates for this is misguided. it's much better to use arena/pool allocators than to malloc/free dynamically, in games.
In my own domain of storage servers and such, I've had success using arenas - per request, usually, but also per session etc. For things with longer lives, object pooling has worked well. Very rarely have I needed much beyond those two, and I suspect the same might be true in games though I've never worked in that space. When I've had to work on codebases that did a lot more "random" allocations I've usually ended up having to spend time migrating toward one or more often both of those approaches just to keep the bug load down and myself sane.
The really cool thing is that supporting custom allocators in a language and its libraries (e.g. Zig) makes both approaches easier, with less boilerplate. I see what's going on with the borrow checker in Rust and - as a long-time fan of static analysis - I understand that it might theoretically be better, but I find it hard to get very excited when I see that the implementation difficulty and cognitive load are so much higher than a simpler approach I've already seen work well.
this is exactly how I feel! in school, the idea of pool/arena allocators wasn't touched upon at all—the options, as I was presented them, were either "manual memory management" (malloc/free/new/delete), "RAII" (smart pointers), or "garbage collection". I had not heard of the concept of "lifetimes", outside of scopes when using RAII, until later, and even then probably only after Rust made that be like it's whole thing.
nobody ever told me, check it out, it's not hard to make like the dumbest possible "bump allocator", where you allocate a hunk of memory and dole it out bit by bit as the application requests it, and then later you can just free the whole hunk at once… or even just move the bump pointer back to the start, and reuse it next frame/request/etc.! that's like 95+% of the reason why people reach for things like garbage collection to begin with, and it seems so obvious in hindsight!
Pretty awesome to see Andy Kelley, Steve Klabnik, and Joe Blow all writing at length about this stuff in one thread, isn't it? In a way, that was my original point. I didn't come here to say it's wrong for Cakelisp to forego tracing GC in favor of nothing at all, but just that the rationale - and maybe compatible solutions to common issues with that approach - should be explained better. I hope the author takes that to heart, because (as plenty of other comments here indicate) without explanation it's a blocker for many people.
Yes, to be clear I am talking about general purpose gc out of control of the developer. Certainly not arguing for the most low level memory management imaginable. All the techniques you mentioned are valuable.
Oh, it's worse than that -- I've seen people pedantically argue that manual malloc and free count as "GC", which you can indeed make a historical argument for, but it's not what most people mean.
To be fair, only a tiny fraction of game developers is working on such games nowadays. It's not like the old days when you HAD to squeeze everything you could out of a 68000 to get anywhere.
i get that but i am not convinced that there doesnt exist some possible gc technology that would make such costs negligible. but thats just a (yummy) pie in the sky
Well, historically and technically, Lisps already became popular half a century ago, then they fell from grace. Also, depends on your definition of popularity: anything outside of Top 10 on TIOBE looks like a rounding error compared to the big guys.
Dylan was a great language - I played with OpenDylan a bit. Scheme with CLOS and gradual typing, and a formal specification, and a stdlib, and tools for modularizing and AOT compiling and linking multi-module projects, hygienic (syntax-case-style) macros, and so on, all in ca. '95! It was a bit too late and Java took over the world before Dylan had its chance... Imagine the world, full of sunshine and rainbows, if Dylan prevailed - instead of the dreary, stifling, primitive language we all know and love (to hate).
Racket is doing nothing similar to Dylan. Dylan was a well defined language for programming in the large and targeted at enterprises. Racket is a laboratory and a workbench for creating languages. It accomplishes this by implementing incredibly powerful macro and module systems. Other notable features of Racket are delimited continuations, contracts, and the Typed Racket dialect. It is targeted at researchers and beginners and has nothing to do with the enterprise. :)
Kotlin is widely popular, official language for one of the two dominant mobile platforms. It accounts for 0.37% of all search results gathered by TIOBE. Meanwhile, languages on the first 4 positions account for more than 50% of hits. Languages like PHP or Go, which you can still call pretty popular are on the order of 1-2 %.
It's completely subjective. The objective metrics (if web search are them, which is kind of dubious) tell us that whatever-language-you-like is probably orders of magnitude less popular than Fortran. The "real" popularity that you experience - how big the community is, how many packages are published, how many success stories are there - heavily depend on where you are, who you know, and what you want.
I've always been troubled by a popularity metric that rewards a language for attracting a lot of novices who need help with it more often. I care more what experts use, but there isn't as much incentive for them to tell us.
Etymologically, they sure do. For all of its warts, elisp has not only many users but many programmers who have created some pretty complicated stuff. It's definitely a contender.
Popular has many meanings. If one takes popular to mean done/used by many then sure. You are right. Filing tax returns is popular by the same definition.
If you mean popular as in the antonym of unpopular I don't think elisp can be called popular. I have written several thousand lines of elisp outside of my Emacs config, and it is probably my least favourite lisp - and I believe most people who know more lisps than elisp think like I do.
> Note that while Cakelisp has "Lisp" in the name, Cakelisp takes some inspiration from Lisp, but is not compatible and does not aspire to become "a Lisp". I was inspired by Naughty Dog's use of GOAL, GOOL, and Racket/Scheme (on their modern titles). I've also taken several ideas from Jonathan Blow's talks on Jai.