Thoughts about choosing a programming language
As our team is currently discussing which language we want to use for the next project, I thought about that question a lot recently. Here I will share some of my thoughts about the criteria I use to evaluate a programming language.
We mostly write the usual kind of server applications, and I think for our use case most languages are good (or good enough) in terms of performance. The focus of this article is mainly on developer productivity, or effectiveness, instead.
Expressiveness and readability
A developer should be able to express his ideas precisely and concisely. Another developer should be able to read that code and immediately understand the ideas of that first developer.
Boilerplate code like getters and setters in Java for example takes up time to read and does not reveal much of the intention of the author.
A mapping over a collection using a lambda expression in a high-level functional programming languages is very easy to understand (given that we know the syntax). On the other hand, doing the same thing in assembler requires a bit of reading to understand what is going on.
So this are two different examples of not-so-expressive code:
- Some languages (or conventions) require us to write some more or less useless boilerplate code for historical reasons
- Sometimes we might need more powerful -- or low-level -- tools, e.g. to improve performance.
Performance critical code often has to sacrifice some of the expressiveness.
To me, the ability to express my intention concisely, and in a way that is still readable (i.e. not "too clever"), is important. Less code generally has less bugs. It takes less time to read and understand it. There is less code to maintain.
Simplicity and the "Principle of least astonishment"
Most languages have some "rough edges". If we're not careful, or we do not understand the language very well, we may write some errors which will cost a lot of money: Either because we waste hours to make our program compile or do the thing we intended; or, even worse, the program will do something unintended at runtime (e.g. crash, or producing wrong results). A good language has very few of those "rough edges". It follows the principle of least astonishment, or, put differently, provides a consistent behaviour of the language features.
Another thing that can make a language simple is a limited set of features. This helps to get started with a new language quickly. On the other hand, advanced features often help me to express my ideas more concisely, or more readable, to improve type safety or performance.
There is a trade-off to be made between
- getting productive quickly
- getting productive in the long term
On the other side, simplicity by means of consistent, well-defined behaviour has (to my knowledge) no trade-offs. It's mostly independent of the size of the language's feature set (although some combinations of features make it more difficult not to astonish the user...). There are some languages which do this well, and others which don't. For example, in the last year I mainly worked with Scala. It has a reputation of being complex, probably mostly because of the number of features it provides. But once I knew the syntax and the concepts, there were very few moments where my code did something different than what I thought it should do.
Before that, I was working with PHP, which has a quite limited feature set. There is a famous article about PHP being a fractal of bad design. This article is somewhat older though, and PHP has improved a lot since then. But it helped me to understand the importance of well-defined, consistent behaviour (which, by the way, is something we should all strive for in our day-to-day jobs when designing our API's!).
Example of an "advanced feature"
We looked at simplicity from two perspectives:
- The size of the feature set
- The consistency of the behaviour of the features.
To think about this topics more concretely, let's consider the concept of type classes as an example. I won't explain what type classes are (if you don't know, look here for the scala version or do a web search...)
Many people consider type classes an advanced feature. But once you understand it, you can write more generic code, which means less code. At the same time, it's hard to use it wrongly. (Opposed to inheritance-based polymorphism by the way -- which I would also consider an advanced feature...). If it compiles, it works as expected, no astonishment. At least that is my experience using type classes in Scala, and I guess Haskell has an even cleaner implementation. It sometimes happens that code using type classes does not compile at first (if the type class instances can not be resolved, or the resolution is ambiguous). But the compiler will tell you about that, and it is easy to fix.
The resolution of type class instances in Scala is interesting when thinking about the principle of least astonishment. While in certain cases it is not easy to understand which instance the compiler will pick, there are well-defined rules defining the priority of the resolution. If the compiler finds two matching instances with the same priority, it does not compile! Instead the compiler shows an error, telling me that the resolution is ambiguous. If the designers of Scala would have decided to just pick the first one in that case, we would have programs that would be very hard to predict.
Matching a language's feature set to the problems to solve
A team should probably use the language which provides a feature set that correlates closely to the problems the developers will have to solve for a certain project. There is a problem, though, at least for small teams: To be able to decide if a feature would be useful, one has to understand it well. This sometimes manifests itself in biased language comparisons. I recently saw a video comparing Kotlin and Scala. The author, after working with Scala professionally for one month, argues that Scala was more complex than Kotlin, and he wouldn't see what benefits Scala has that justify that complexity. Without a background in another functional programming language, it's understandable that one does not realize the power of something like type classes or higher-kinded types after using the language for one month. I don't know much about Kotlin myself, so I'm not making any statement here on the question which of the two languages is "better"... My point here is that one cannot judge whether or not learning an advanced language pays off in the end, without really understanding its features. Which is unfortunate.
Re-usability of the language across platforms
While it is certainly good to know many different languages, a developer can work more effectively when he's working primarily with one or two languages. At least that's my experience. I tend to forget the precise syntax, rules, conventions, etc. when I don't work with a language for some time. In terms of effectiveness, it may be beneficial to have one language for multiple platforms. Examples may be:
- Scala(.js) for the browser and on the server
- Kotlin for the server and an android app
The role of the type system and the compiler
There are different opinions on the question whether static type systems increase of decrease the effectiveness of developing. I consider languages with not only static, but also strong type systems of great help. It may be a kind of "advanced feature" which takes some time to learn, e.g. to "parse" type signatures which involve type parameters. The advantages are:
- type safety at compile time (find bugs earlier)
- types help tools to help you (e.g. refactoring, code completion, type holes, ...)
- types may be the basis for language features or code generation tools
- type signatures may help understand what some piece of code is doing
This are all advantages that directly help me to write and maintain software effectively.
Tooling support and libraries
Unfortunately, when switching to a new language, learning the language itself is often just the beginning. To efficiently use it, we must get familiar with its ecosystem. For me, it's a big plus if we can re-use existing libraries or tools which we already know. That's why I would prefer switching from Java to Kotlin, over switching from Java to C#. Probably. If I wouldn't know about Scala :-)