The original .NET garbage collector was written in Common Lisp
twitter.com
twitter.com
That's the reason the .NET GC source code is still in one single massive .cpp file. I mean, look at it [1]. Github even refuses to display it.
[1] https://github.com/dotnet/runtime/blob/master/src/coreclr/sr...
The main thing is dotnet had this figured out 10 years ago while Java is still struggling.
.NET, by putting all their efforts into just one GC, has absolutely reaped a lot of benefits in terms of working well without any fuss. .NET also has better ergonomics around memory management. For example, it has much clearer and stronger guarantees about what state the runtime environment will be in after an out-of-memory error. On the other hand, I'm sure that making these guarantees have at times constrained the things that .NET can do with its garbage collector.
Java, by making the GC a plugin, has certainly spread its efforts thinner. It's also turned the Java-facing side of the memory management subsystem into a somewhat scary and mysterious black box, since that very swappability means that the application itself can assume almost nothing about its behavior. On the other hand, if ops wants to put the effort into tuning it, Java lets them have a lot more ability to control an application's memory and performance characteristics in production. It also leaves a lot more room for boutique JVMs with their own special memory managers.
Which approach to prefer is, I think, as much a matter of personal or organizational values as it is about technical merit.
Edit: and by this I don't mean that one of us is wrong. I mean that it might depend more on the application and its memory allocation patterns than the runtime.
I found it to be a much more efficient way to work iteratively, because software that's written in a lisp is fundamentally more amenable to change than software that's written in a language like C.
Today, not so much, because the gap has closed a lot. IDEs and improvements to the ergonomics of lower level languages have made them a lot more convenient to work in end-to end, and cheap memory and compiler improvements have made it a lot more feasible to just ship code that was written in a higher level language.
https://devblogs.microsoft.com/dotnet/work-flow-of-diagnosin...
What requires expert level are the error messages, the quality of generated code and runtimes performance.
The responsible professor considered the final project would be too easy for anyone using them, and he was kind of right.
The other reason the code is somewhat clean (albeit long) is probably that there's been 20 years of work on it. And it probably wasn't that long in the beginning.
As for C#, the things we don't support are:
- yield statements: codegen for that is not fun to do on your own and hand-writing an IEnumerator is annoying, but not too bad. And back when we started we couldn't get the generated code from Roslyn, maybe there's a way by now to do that.
- async/await (could nowadays be converted into pretty much the same in JS at least, but Task/Promise/CompletableFuture work slightly differently anyway and there aren't that many places in the codebase that need this, so they can be patched)
- dynamic (no reason to use it, thus no reason to support it).
- Some generic constraints, e.g. new(): We currently emit JS, TypeScript, Java, and Python, along with a number of internally-relevant output formats like documentation – and .NET generics already don't map well to Java generics (or vice-versa), and are mostly useless for JS and Python, anyway.
- goto, unless for fall-through in switch
Most “normal” things like types/members/statements/expressions do work properly and the subset isn't too bad working in (it's also not a particularly small subset, of course). A lot of additional work comes in getting the necessary parts of the BCL to work on the other side of the conversion, of course, but that is a separate concern next to language features.
[0] https://twitter.com/Suchtiman/status/1265152818131894272
The blog author worked for TI on their Lisp Machine and for Lucid.
That's a great way to prototype -- use what you know and then rewrite (or in this case transpile?)
I wish...
Yeah, I've heard that one too many times. It amazes me how much that experience changes how a developer prototypes.
http://napkinlaf.sourceforge.net/
Edit Here we are: https://headrush.typepad.com/creating_passionate_users/2006/...
I absolutely don't meat starting a "holy war", the question is intended purely for sake of learning more about Common Lisp, what features does it have which make it a right tool for this job in particular.
Plus Scheme has no batteries included.
Scheme is rather functional by nature and has a core reliance on things like proper tail calls and continuations. These require elaborate, whole-program compilation so they can be optimized away. A side effect of that kind of in-depth manipulation will necessarily be output code that is far removed from the layout of the original source (making it harder to understand what is happening or hunt down bugs).
Common Lisp can do functional things, but the core of the language is much more imperative. You'll wind up writing looping constructs instead of tail calls (tail calls aren't actually part of the spec though some/most implementations support them). CLOS can probably map onto C++ classes without too much trouble as well. These lower-level constructs make it easier to reason about performance.
That alone isn't everything. Common Lisp allows you to provide type hints and type specifiers to the compiler. Aside from the speedups they allow in CL, they would certainly make things easier when translating to C++. If that isn't enough of a boost, basically every major CL implementation allows you to define an inline assembly block. These things certainly could be done in Scheme (for example, typed racket), but the reader and unhygenic macros in CL certainly make it easier.
If one chooses to pour time into improving the GC instead of the language, the benefits are reaped both by existing projects and new projects by old-school developers.
For example, System.Text.Json is the default in ASP.NET Core 3.x, updating to 3.0 would use it instead of Json.NET unless you explicitly opt-out.
In the same way many APIs are starting to accept Span<T> in addition to IEnumerable<T>, Lists or arrays, so it's easy to opt-in into that. Other APIs are using it internally to avoid copies, so you don't need to do anything to get benefits.