Although I haven’t coded for work for a long time and have inexorably gravitated towards technology practice management, I’ve been thinking about what to use for rebuilding a set of personal projects (including this site) and tackling a few new ones.
What I’ve always tried to do is to have a set of tools that I can rely on for long term, long lifecycle stuff, simply because it sometimes takes me literally forever to complete a personal project, and it then tends to run for (also literally) nearly a decade.
And over the years, I’ve found that programming language selection tends to be critical to ensure that kind of longevity (although I prefer to think of language runtimes). And some of the stuff I’ve written just didn’t die when it was supposed to (for instance, Perl code tends to last forever, although these days that’s mostly because it’s faded into obscurity).
So there has to be some sort of balance here, and right now it makes a lot of sense for me to rewrite some things in a more modern programming language.
Five Pillars Of Software Longevity
Whenever I try to break down what I need in a programming language, I end up with the same five key attributes:
- Stable and mature (i.e., no community dramas, no weird changes to the language spec, and moderate assurance I will be able to recompile things or use the runtime ten years down the line)
- Good library ecosystem (MUST have batteries included to some degree, too)
- Fosters easy to read code (i.e., low cognitive impact and codebases that I can go back to months later)
- Good performance (preferably compiled to native code, good concurrency support and efficient memory use)
- Low-overhead tooling, with the ability to cross-compile (ideally standalone) binaries for ARM, Intel and RISCv (in roughly that order)
Python satisfies most of these criteria, obviously, but it’s just not fast enough for some of the things I want to do, and I’ve been using [Java] on and off, but it doesn’t scale down into low-end hardware like I need to. So what else can I use?
Sadly, No Parentheses Made The Cut
Even though I have been writing a fair amount of Fennel (and quite enjoy it), and even considering there are promising LISP-like languages like Janet out there, I have yet to find a LISP that does everything I want, so I must begrudgingly cast those aside.
Mainstream Is Safer, Right?
So let’s go over the mainstream stuff, including some that I’ve tried to use recently:
- C# is something I’ve been looking into a fair bit, but which requires a lot of time investment (and overhead) to get things going for me. I kind of like .NET Core 6 and can see myself using it, but I just don’t have the time to learn another [Java]-like ecosystem and it worries me a bit that things will keep changing. I do like that it can finally target various architectures and build standalone binaries, but I just… can’t. I need something that has less overhead.
NodeJSprogramming model is still an event-driven, single-threaded nuisance, and its saving graces of performance and ubiquity barely make it tolerable. But what really kills it for me is that unless it’s being used in well-defined, restricted contexts (like Node-RED) I really can’t rely on it for things that need to last years.
- Python (with or without C mixed in) is pretty much always going to be my go-to language for quick prototypes since the standard library and ecosystem make me very productive in short bursts, and it scales well when you’re a single, unhurried developer hacking slowly away at a problem for months. It is a pretty good candidate as a “forever language” because it’s very simple and evolved fairly slowly and steadily (although I’m openly biased here because I had effectively zero practical issues with the shift to Python 3 and quite like
asyncio). It’s what powers this site (and many of my other projects) because I know it will work for years, if not decades, but it is not exactly fast (it just helps me deliver results faster, which is arguably better but not what I want sometimes).
- Rust is something I can read, and I have a lot of interest in, but which has a fair bit of cognitive load and does not lend itself to short bursts of coding scattered throughout weeks–it’s just too easy to lose track of what you were trying to do, and the library ecosystem still requires a lot of trial and error to get into. Hunting and pecking to find active, well-maintained libraries has been a significant problem in my attempts to use it, and although I’m pretty sure it has a future, I have to give it a pass for the moment.
- Swift is… No. I’m not even going to go there.
- Go is now (shockingly for me) well over a decade old and has even deeper roots if you remember Aleph and Plan 9, so most of the kinks have been ironed out. It now has tolerable dependency management (including pinning and vendoring, which is important for me since I don’t like it pulling stuff from arbitrary
gitrepos) and the ecosystem has evolved tremendously–and yet its nice, modern standard library is still plenty useful. The error handling is a major nuisance and very much a polar opposite to its ascetic approach to typing and interfaces, so I find it hard to tune out and enjoy writing code in it, but when it comes together, the results typically fall into the “lean and mean” category. I wish it had better abstractions in general (not just generics), but I have quietly been writing more and more stuff in it.
And guess what, I think that’s the actual answer. I get really annoyed at some of Go‘s quirks every now and then, and I still do a double take when I read some function signatures, but… It works.
I can do pretty much all I think I need in terms of low-level coding on it (and I can always use C/C++ instead, since I haven’t lost touch with it), so… maybe it’s finally worn me down into acceptance.
I still wish there was something better in the LISP universe (something that was at least as popular and could scale from little development boards to clusters at bare metal speeds), but I guess Go will have to do.