This article is part of a multi-part series about programming languages.
In the last article we discussed the two most important points regarding The choosing of a programming language: resource management and type system. In this post week arrive at the effects of different language design decisions which have less impact on the business but more impact on the developer.
As with the first post, This post is based on my opinions and observations, with decision points having higher impact coming first.
I should start by saying that not all languages fit into the guidelines laid out here.
Decision Point: Platform
After resource management and type system, The platform upon which the language runs comes next.
Before we begin we must define some terms.
We define "developer code" to be the code which We will write or include as part of our application when we write in any given language.
We define "incidental code" as code written by the language designers which must interact with developer code in order for that code to work in a given language.
We define "platform" as a program derived from the incidental code which the end user must install in order to use the application.
We define "platform size" here to mean The ratio between incidental code and developer code.
Languages fall into three different categories with respect to platform: platformless, and hosted.
Languages which do not require the end user to install a platform fall in the first category. By definition, these languages create programs that are much easier for the end user to install and use. They are often used for projects where distribution is an important part of the value pipeline. We should not use languages which require a platform of the user if we are creating distributable programs.
Sometimes this rule can be followed by languages which have a platform later by bundling the platform with the code. There are tools in languages which typically require a platform to be shipped without it such as Grail VM's native image in Python's cx_Freeze,. Programs which are built on a platform must either require the user to install platform or bundle it with the application. Examples include Vagrant and LibreOffice.
Sometimes this strategy can work, but these strategies are all escape hatches to be used in case the wrong language was chosen up front. They should not be relied upon in a decision about which language to choose for a distributed application before anything is written.
A recent trend has languages taking this into account. Thus many new languages have no separate platform. Go is the poster child here, but other languages also come to mind, such as Crystal and Janet. These were designed up front to be platformless. Of course, languages with very little to no incidental code are also platformless and include languages such as C, Rust, Zig, Hare, and others.
A big downside to a platformless language is that the specifics of the machine architecture is more exposed to the language. The language must now deal with writing code that is run on several different types of machines and be compiled for several different operating systems and still have it run well. This complicates the build, and is more difficult to get right from the language designers perspective. It also complicates the language which must now deal with things like unsigned integer types (if any integer types are allowed at all). This lack of machine abstraction can cause some of the code to be machine specific. It is therefore slightly less comfortable to work in a language that does not run on a platform.
Another large downside of platformless languages is that they tend to be typed. Thus a platformless language is really only something that can be used if typing has already been chosen. This choice itself is a large language decision; see the previous post. However, in situations where the program is being distributed, type systems are often an asset anyway. Particularly for open source software, type systems ease coordination between different remote groups, those who use the software and those who maintain it.
Still, modern languages have done a pretty good job of mitigating these pains. Golang's build tooling is second to none, and it has been specifically designed for fast builds. Janet is dynamically typed. Often the needs of the programmer can be met while still choosing a platformless language.
When distributing programs, choose a platformless language. Being closer to the machine, they tend to run fast, modern examples tend to still be relatively easy to use, and of course, they're easily distributable.
Hosting a language on a platform allows the language to dispense with dealing with all the sharp corners of modern machine architectures. The code becomes simpler, since it is only targeting its platform. Further, the platform can be optimized to run the language instead of the other way around. This allows the language to be more designed around the programmer's needs and less around the machine. Hosted languages are thus more comfortable to work in and and tend to have good tooling and a nicer developer experience.
Code written in hosted languages also can potentially last longer with less maintenance. This is because machine targeting is a task that has been offloaded to the platform, allowing for less code churn in the developer code itself. This is a huge upside for businesses of any size. Small businesses benefit from less code churn, and large businesses benefit from code longevity.
The platform also provides features that are useful in a deployed language. Because the platform can be installed and configured separately from the code itself, it provides several avenues for operations teams to improve or the health of a running program. Garbage collection tuning, automated profiling, and instrumentation, are all easily possible from the ops side, often with the need for coordination with the developers. The tools are usually absent or much more difficult to use in a platformless language.
The downside is a hit in performance. Garbage collected hosted hosted languages are usually fast enough for modern workloads, but if a performance it is also incurred when memory is managed via reference counting, performance can be a problem. This is often not a deal breaker, but something to keep in mind.
When deploying code, choose hosted unless there are significant performance requirements. It provides tools for both operations and development teams to be productive and help each other.
Decision Point: Ecosystem
Many will be surprised that ecosystem is so far down this list, but it's in the right spot. People think they look at this first, but really they often implicitly choose resource management, type system, and platform first without realizing it. Only after choices are winnowed down is ecosystem considered.
The larger the ecosystem, the easier it is to develop in the language. Development in languages with a smaller ecosystem is not impossible but requires more from the developers. Unlike the other items on this list, they really aren't any downsides from a business perspective to working in a language with a large ecosystem. Network effects are strong in helping people choose a language because of this fact.
That said, smaller ecosystems do make for tighter communities. It's actually usually easier to find live help on a chat channel with an expert in a smaller community. This is less available than just looking something up on the internet, and often less convenient. Thus, businesses for profit usually choose languages with sufficiently large ecosystems, while hobbyists often like the smaller community feel.
Languages need a minimum viable community in order to survive. Thus includes libraries and documentation which meet a majority of the developer's needs. Such communities often must provide support for the following functionality in order to survive and thrive:
- Web framework
- SQL ORM
- JSON parsing
- Sub-process running
- Regular expressions
- Basic data structures
- File and network I/O
Choose large communities whenever possible. Engage actively with small communities actively when necessary. Ensure there is proper support of the needs of the program to be written before choosing a language.
Decision Point: Esoterics
There are all sorts of other language features out there. Browser runnable, event loop based, homoiconicity, hygenic macros, linear types, lazy evaluation, the actor model, stack based, functional, etc. These typically impart some strength to the language, making working in some domain much easier. The drawbacks are usually all the same: high learning curve. The gorilla in the room here is functional languages. They typically make the programmer more productive in general, with the drawback of high learning curve.
This is another trade-off between a large team and a small one. Small teams have right coordination and this helps overcome any learning curve problems, while increased productive may help a start-up bat above its weight and keep up with larger corporations. Conversely, large teams already have significant challenge with coordination of effort, exacerbated by the high learning curve.
Choose languages with lower learning curves for large teams. For teams that that plan to be small and stay small, esoteric languages are often the right choice.
No Free Lunch
One of the biggest themes of this blog series is that it is impossible to choose the correct language for all developer or business circumstances. The hardest challenge of this truth is that circumstances change. Choosing a language might be correct for the present but might be disastrous for the future. The strategy behind dealing with this hard truth is the subject for the next post. Lots of different factors need to be thoughtfully considered before choosing a programming language for a project. Most of those factors should be business-oriented but the developer should also be taken into account.