Kotlin programming language positions itself squarely as an application programming language, but searching for this term does not give a lot of insight. However, you can find a number of articles on what it means to be a system programming language and a scripting language. Amazingly, but it seems that just a little over 20 years ago the main dichotomy in programming languages was mostly between the latter two.
Back then in early 1990s many end-user applications were written in C++ that was and still is considered a prime example of a system programming language. On the other hand, over the course of time the concept of a scripting language had become strongly associated with dynamically typed languages and the old vision of writing end-user applications as scripts on top of systems had encountered a harsh reality of big and diverse development teams writing larger and larger applications that the rapidly growing industry produced.
The separate niche of application programming languages, distinct from both system and scripting languages, was created in its modern form by Java programming language that found a good balance of language features for application development and influenced a whole slew of other popular languages. I’ve covered some of that in “A Tribute to Java” story.
But what does it mean to be a modern application programming language? What is common to all application programming languages? Let me present the mental framework that helps me reason about those aspects of programming languages.
Hierarchy of developer’s concerns
Software developers have to be mindful of lots of concerns when they write the code. We can group and organize these concerns into the following hierarchy and represent it as a pyramid.
First and foremost developers should understand their problem domain so that they can create a formal model for the problem they are tasked to solve using the concepts that their programming language provides.
Then developers implement appropriate algorithms to solve the problem in terms of the model they’ve formalized. Various algorithms that developers write need to somehow represent in memory the data they work with and ultimately get compiled to machine instructions for execution.
Being a vastly simplified representation of developer’s concerns, this hierarchy still offers insight into different kinds of programming languages. Developers are human beings and they cannot possibly know and keep in mind everything that is going on with their code. A developer focusing on machine instructions cannot be expected to be a great expert in the problem domain at the same time. The limitations of the human mind directly impact the design of a programming language. As Harold Abelson, the author of “Structure and Interpretation of Computer Programs” famously observed:
Programs must be written for people to read, and only incidentally for machines to execute.
Thus, a well-designed programming language cannot include every conceivable feature for every use-case or it becomes too big for humans to master and understand. A programming language must cover the whole hierarchy of concerns and execute some machine instructions at the end, but it must choose to focus on some aspects and is forced to gloss over others to keep its own complexity manageable. Different programming languages can be roughly overlaid onto the hierarchy of developer’s concerns in the following way:
At the top of the pyramid we find 5th generations languages (5GL) like SQL and other domain specific languages (DSL). They focus on domain and models and ensure that developers do not have to think much about algorithms and anything below them in the hierarchy, giving none or limited tools to tweak those.
Application programming languages are general-purpose and cannot cater to a specific domain as well as DSLs do, but they are still apt at expressing various domains and have lots of tools to write various algorithms to solve the problem. The lower levels of hierarchy are not as focused in application languages — they give less flexibility in memory representation and rarely support direct control of emitted machine instructions.
System languages focus on efficient algorithms and optimal memory representation, often to the level of bit layouts. They might even provide the ability to specify concrete machine instructions to manipulate data, while true low-level languages (like assembly) are all about memory layouts and machine instructions. It does not mean that low-level languages don’t have tools for writing algorithms and up, but they focus their features on the lower levels of hierarchy.
Systems vs applications
This framework gives us a visual tool to separate a system programming language from an application programming language. In modern world the litmus test is a programming language’s support for a range of memory representations.
Take a look at C++, for example. It gives developer a plethora of means to control memory layout, parameter passing, and memory reclamation. In modern C++ you can choose to work with your data by value or via raw pointer, reference,
std:shared_ptr. Which one to use? It depends and it is something developers have to think about when they are writing their code.
Application programming languages, on the other hand, offer a much smaller assortment of choices. It limits the ability to fine-tune the code, but also frees the developers’ mind from those concerns to better focus on an application problem they have at hand. How much time do we want to spend on copying and moving, ownership and borrowing, weak and strong references, the difference between values and pointers, etc when designing a typical API? None of it.
Don’t take this framework as a clear-cut way to sort the languages into buckets. It is not always as pronounced as in Java (application) programming language vs C++ (system) programming language. There are a variety of modern languages that spread their features across the hierarchy becoming a hybrid of system and application development languages. However, Kotlin aims to focus primarily on the high-level concerns of application developers and ease the mental burden of lower-levels as much as possible, so that the code has more substance related to the problem at hand and less ceremony in it.