My Programming Language Wants

Several times now I was asked what exactly makes for a good programming language in my opinion. I never could articulate an answer that I was truly satisfied with but then again this seemingly innocuous question is actually very hard to answer precisely and succinctly. The reason is that the question is quite open and thus can be answered on different levels. You can provide a vague answer describing the character of a good language and the feelings such a language evokes in yourself or others when using it. Although perfectly fine and in fact the only sensible level for a non-technical inquirer I am not really satisfied with such an answer. A more precise approach would be to meticulously enumerate the features that make for a good programming language. Even this would be glossing over the important fact that a programming language is more than the sum of its parts. A programming language is designed and not simply assembled. I still like the more precise spirit of the latter approach which I am going to take in this article and I would like to replace the adjective “good” with the adjective “convenient”. In the following I will thus enumerate those concepts or features that together lead to a general-purpose programming language which is convenient from my point of view.123

Before I begin let me cite a paragraph from the first chapter of SICP that states the actual purpose of powerful (here convenient) programming languages and the three general mechanisms they must provide:

A powerful programming language is more than just a means for instructing a computer to perform tasks. The language also serves as a framework within which we organize our ideas about processes. Thus, when we describe a language, we should pay particular attention to the means that the language provides for combining simple ideas to form more complex ideas. Every powerful language has three mechanisms for accomplishing this:

  • primitive expressions, which represent the simplest entities the language is concerned with,
  • means of combination, by which compound elements are built from simpler ones, and
  • means of abstraction, by which compound elements can be named and manipulated as units.

I entirely concur with this viewpoint. Serving the programmer is a programming language’s primary purpose and serving the computer the secondary. The three mechanisms are general in the sense that they can be rediscovered on different levels in a convenient programming language’s feature set. To give you the simplest example, numbers are primitive expressions that can be combined to compound expressions with operators which in turn can be abstracted by giving them a name. I just wanted to cite the above part from SICP in order to put the subsequent list into an appropriate frame of understanding. That is, the features are useful because they make programming more convenient for the human programmer and the three general mechanisms are a common pattern in most of the features.

Basic features:4

Advanced features:

In addition to the above features a convenient programming language’s syntax and semantics must be consistent, succinct and reasonable. The syntax must be easy on the eyes as well, as much as possible should be an expression for better nesting capabilities and there should be default syntax sugar to improve programming convenience for often encountered use cases. The syntax should neither waste too much vertical nor horizontal space for typical programs. The features simply must be implemented in such a way that there is a beautiful balance among them. Do the language features play nicely together and is it clear what idiomatic code is? Both parts of the question should have the answer yes. On the other hand, programming language design is all about trade-offs so achieving a beautiful balance or, worse, a balance most people will like is probably impossible. 282930

This article dealt with syntactical and semantical concepts or features that I want to have in a programming language to call it convenient. The next article is about a programming language’s ecosystem which is arguably more important than the language itself. 31

  1. In the end, the aspiration to be fully precise is futile and misguided anyway. What does “good” in the context of programming languages even mean exactly? It is just the nature of programming languages that denies an objective and precise notion of goodness. As hinted in the introductory paragraph liking a programming language is not just about its technical features but about taste and a lot of other soft factors. As the saying goes there is no accounting for taste. This is why I like the adjective “convenient” as it has a subjective flavor and puts the user first. Another alternative would be “usable” but it seems to me that “usable” would steer the focus a trifle too much in the futile objective direction. 

  2. Another problem with answering such a question is that your preferences are mostly manifested implicitly. It is not easy to name them offhand. Some of your preferred features you do not even know yourself until you lose them e.g. by switching to another language and longing for them. This article is thus also an attempt by me to make my implicit preferences explicit. 

  3. The primary source for this list is the great paper Fundamental Concepts in Programming Language by Christopher Strachey. Other sources include SICP and Wikipedia. I was also inspired by Chris Done’s blog post One step forward, two steps back

  4. These features are inspired by structured programming, the commonly needed structures for a language to be turing-complete, and the other mentioned sources. The basic features are those that almost any programming language has. 

  5. Identifiers, numbers, strings, characters etc. 

  6. Arithmetic operations, comparison operations, boolean operations, bitwise operations etc. 

  7. Or something comparable in terms of computability like first-order functions and recursion. Simply to achieve turing-completeness. See also BlooP and FlooP 

  8. E.g. looping constructs (for loop, while loop), shortcuts (break, return) etc. 

  9. First-class functions imply higher-order functions

  10. Most commonly implemented by closures

  11. A succinct syntax is very important as anything else would contradict the purpose of anonymous functions. 

  12. Support for free variables and first-class functions actually imply recursion. 

  13. That is, functions which have no return type (void) and who are just executed for their side-effects. In my book, a syntactical distinction between procedures and functions is not necessary. 

  14. Very important for the usability of the language and its APIs. 

  15. User-defined compound data types, abstract data types etc. 

  16. Local type inference is more than enough since one generally wants to explicitly list the types of top-level constructs anyway. 

  17. Different parameters for each case must be allowed and there must be a type switch with exhaustiveness checking. 

  18. “Tuples” 

  19. “Static dispatch” 

  20. In the form of dynamic dispatch, ideally multiple dispatch. On the other hand, one can make the argument that multiple dispatch is anti-encapsulation. 

  21. Or something like concepts in C++. Basically, constraints on generic parameters. Derivations like in Haskell should be supported. 

  22. E.g. namespaces, private and public elements. Related to encapsulation

  23. Templates, macros. 

  24. Or just some way of using user-defined symbolic infix operators - best with multiple dispatch. 

  25. Such a “dot-call” or “threading” syntax makes the direction of a flow of calls natural, saves redundant brackets, makes fluent interfaces easy and provides context that can be exploited by IDEs in order to provide autocomplete suggestions with inline documentation which is not just “nice to have” but a pretty big deal. Related to extension methods

  26. Mostly implied by macros. 

  27. Mostly for null checking. The more convenient alternative to option types. 

  28. Of course, a programming language may have many more features (not in an arbitrary manner!) but without most of the above I probably would not call the language itself very convenient — it can nonetheless be very useful. Truth to be told, there (almost…) does not exist a language I would call convenient (or it is convenient but has other severe drawbacks) although I do not even find my list sophisticated. But this is no problem as for getting a specific task done the language’s ecosystem is much more important than the language itself anyway. 

  29. I actually also like a language to be as static as possible even though the above features do not require it (except maybe overloading but I believe Smalltalk has in fact overloading and maybe parametric polymorphism but the problem it solves would not even be an issue in a dynamic language anyway) for a lot of reasons that would be too numerous to explicitly list here and so are reserved for another article. 

  30. I actually omitted features in the list that I find cool but which would really complicate the implementation or whose potential benefits and drawbacks are not entirely understood yet. You may thus see the list as a lower bound if you like. 

  31. Another interesting question to think about is which features or combination of features a programming language should definitely not have. Concrete examples could be derived from existing languages which is in fact quite often done in practice. One exemplary point I made in previous articles is that I find 1. laziness by default and 2. pureness by default together with monadic code for I/O to be bad tradeoffs. The scope of a general analysis of “bad” features would be quite big though.