I seldom digress about programming languages, since I believe they ought to fade into the background and be largely interchangeable as the systems you build become more complex. But there are some sweet spots that are worth delving into, and any architect/developer/engineer worth their salt should be constantly reevaluating what they know and learning new stuff.
My “personal stack”, if you will, has changed a lot over the years, and every year or so I take a look around and try to strike a balance between pragmatism and curiosity. Or, rather, I try to do so, and often fall short due to practical constraints.
This year I started taking a long, hard look at server-side application stacks, with the goal of picking a new language to invest some time in. In particular, I’ve been looking for stuff that is/has (in no particular order):
- Sane CLI tooling (I’m not big on IDEs, and prefer customizable editors)
- Great libraries (good standard library, wide-ranging ecosystem and, above all, sane dependency management)
- Good performance (near to native)
- Great support for concurrency
- Easy to deploy on both Intel and ARM Linux
I’m a bit late (it is mid-April, after all), but, again, real life intervened. But for the kind of stuff I want to do on the server side, there were only three main options:
C# has been an obvious choice since I joined Microsoft. The language itself is quite straightforward, but there were two things that repeatedly put me off: The Java-like verbosity and the shift towards .NET Core, which I’ve been following since the beginning.
Investing my personal time in building server-side apps based on C# would mean wrestling with the current impedance mismatch between .NET Core and Standard, and I fear ending up in much the same conundrum I had when Java was iterating between 1.2 and 1.5 – a period that many developers these days never had to go through, but that had entirely too many breaking changes for my taste.
.NET Core’s CLI tooling is pretty good1, but I would prefer it was self-hosting in ARM, and some decidedly odd omissions in the initial standard library (like the continued need to resort to
System.Json started being ported across from Mono in Summer 2016) didn’t help, so my it took a while to dispel the impression left by my earlier attempts.
But the deal breaker for me hasn’t been
CoreFX (or former gaps in it, which are being filled in at a consistent pace), but rather the difficulty of muddling through
NuGet and finding suitable libraries for my purposes, often with contradictory dependencies and hidden platform-specific requirements2.
Once you’re lucky enough to have that sorted, deployment becomes mostly a matter of shipping the right DLLs alongside – which is easy enough with Docker, of course.
What also wasn’t easy (at least for me) was dealing with concurrent tasks. I fidded around with
System.Threading.Tasks for a bit and found it to require just as much ceremony as Java’s futures. Given I’ve been spoiled by Clojure’s take on concurrency and
core.async, everything else (even
asyncio in Python) seems a tad off by comparison. Toss
async/await into the mix, and even reading code starts requiring a bit too much mental overhead.
In the end, through, C# is something I end up dealing with on the clock in a near-daily basis, and my goal is to pick something for me to use on my own time. Also, I find it healthy to keep an open mind.
I’ve been messing about with Go for a few years now, and have a few private projects that run 24/7 and have given me zero troubles.
The standard library is nothing short of amazing (even though it too has a few gaps), performance handily trounces mostly everything else, and (a key aspect for me) memory requirements and handling are pretty close to ideal – a single 9MB binary has been running on one of my boxes for the past couple of years and never gone past 200MB of RAM, even though it is getting constantly hammered.
I do wish the language was a bit more sophisticated (no, I’m not going to gripe about the lack of generics, what really annoys me is inline error handling), but a recent foray into C++ made me appreciate interfaces and channels even more.
In particular, the way channels and goroutines work together makes it much easier for me to deal with concurrency – and a nice bonus is that I never have to keep track of
async calls, which in other languages (like Python and C#) have a way of evolving into completely segregated program flows.
Deployment is trivial (contrary to popular belief, Go “single binaries” often require a few extra libraries, but they’re universal enough not to require bundling), and cross-compilation comes out-of-the box.
But the icing on the cake is that you can target (nearly) any architecture by setting
GOARCH4. Paradoxically, even though it is pretty much future-proof as far as ARM64 support is concerned, there were a few hiccups with (legacy) ARM support recently, and I have to fiddle around a bit to build
However, where Go completely breaks down for me (and the biggest reason I haven’t invested more time in it) is its borderline insane dependency management and conspicuously lingering lack of a consensual way to vendor dependencies and manage my own workspaces – I’ve done my best to reach an agreeable compromise, but until
$GOPATH is excised or neutered, it’s a constant annoyance that I’d rather not have to deal with.
I’ve never been your typical Java guy (I find most enterprise frameworks and the degree to which they were shaped by rampant abstraction somewhat frustrating), but I grew to like the JVM (lately because of Clojure, but mostly because I had a few years of exposure to unconventional ways of writing high-performance Java).
What I don’t like about Java is the amount of ceremony involved in writing (and reading) code, which I’ve always considered to be needlessly verbose.
So when Kotlin came along, I immediately liked it because of its conciseness. It reads a lot like Swift (which I’ve played around with, but not for back-ends), seems to have a pretty comprehensive runtime (complete with helpers to paper over some of the annoyances of Java), and has full interop, something I am used to having in Clojure and that makes it trivial to re-use a bazillion existing libraries.
But, again, most important of all was that I found I could actually read Kotlin code with much less cognitive overload than Java or C#. This is super important for me these days, since I have very little time to code on my own projects and need to be able to jump back in (and out) at a moment’s notice.
Immediacy is key, and (again, something that Python and Clojure spoiled me with), Kotlin has a pretty decent REPL. Plus there’s the nice side benefit of forcing me to keep myself updated on the Java ecosystem (which is also one of the reasons I took up Clojure a few years ago).
The only thing that annoyed me a bit so far is that the (experimental) support for coroutines seems to be going down the
async/await rabbit hole, but out of my little checklist, all that I was really missing was sane CLI tooling5. Fortunately, you can use
Yes, I know I wrote “sane”, but I’m a CLI guy and like my tools to be predictable and boring (to the extent that I still prefer using
ant for building my Android apps rather than rely on an IDE), and it took me around 20m to figure it all out.
I’m a bit miffed that a “Hello World”
.jar takes up over 900KB, but I haven’t delved into possible optimizations and it’s a nice compromise considering the ease of deployment of
Sure, you need to pack the JVM into your Docker containers too, but mine clock in at ~190MB, which is OK, and you don’t need to use containers all the time…
What about Elixir?
So I’m going to dive in and see where Kotlin takes me.
Incidentally, Visual Studio for the Mac is great, and I’ve been using it to play around with Xamarin for a few toy mobile apps. But doing server-side stuff on it is… just too weird. ↩︎
However, I must admit to having spent a couple of entertaining evenings playing around with Unity and Xamarin. Horses for courses, if you will. ↩︎
This is the main reason I keep using it for small projects, provided your dependencies are sane, of course, and can be easily rebuilt for the target. I had a few issues with image processing stuff, to the extent that I ended up building natively on ARM. ↩︎
As much as I like what I’ve seen from JetBrains’ IDEs over the years, I don’t want to be dependent on them (or any IDE). I could lay the blame at the feet of the Eclipse/Visual Studio wars, but it boils down to eschewing “autocomplete cargo culting” and preferring very fast editors rather than sluggish behemoths. ↩︎