Image of Why are there so many programming languages?

ADVERTISEMENT

Introduction

Simply put, the reason why there are so many programming languages is because each language's design involves compromise. Developers want a language that is easy to work with, highly performant, and supports the features they feel are most useful. However, not all are possible simultaneously, so language designers have to choose which to prioritize. Thus, not every language can satisfy every developer's preferences. Fortunately, the accessibility and maturity of modern language creation tools (lexers, parsers, compilers, transpilers, interpreters) makes it easier for developers to create new languages specific to their own needs.

However, the resultant surplus of new programming languages has not been met with a rapid die-off of older programming languages. Mainstays like C/C++, Java, and PHP are well over 20 years old and are showing no signs of slowing down, thanks to their reliability and strong community support. Even older languages like Fortran, a language first released in 1957, still see frequent use in government and financial systems, where the cost of rewriting such massive, mission-critical software in a new language outweighs the benefits.

Of course, all of these languages have pros and cons. In this article, we'll discuss some of the reasons there are so many programming languages to choose from.

Compiled vs Interpreted Programming Languages

Perhaps the most influential decision in a language's design is whether it will be compiled or interpreted. Compiled languages, such as C++, Go, and Rust, are translated into machine code ahead-of-time, yielding an optimized executable file. Interpreted languages like Python, JavaScript, and Ruby are translated to machine code at runtime.

Compiled languages tend to be faster than interpreted ones. Since the process of converting source code to machine code does not need to occur as quickly, compilers can introduce optimizations that interpreters cannot. However, this means that developers using compiled languages must wait longer than interpreted language users between making a change in code and testing the program.

Another difference is that compiled languages tend to be statically typed (each variable holds a predefined type such as an Integer or String, known ahead of time), while interpreted languages are typically dynamically typed (a variable can store a value of any type). Static typing can be seen as overly restrictive by some developers, but it reduces the number of errors that can occur at runtime, since most mismatching type errors will be caught at compile time. For example, attempting to add a number and a memory address together will cause a runtime error in a dynamically typed language, but a statically typed language can detect and warn about this error before program execution.

Handling these kinds of decisions is part of the reason so many new languages have been created over time. Developers requiring the fastest possible program execution may enjoy the ease of programming in Python, but that language's interpreted nature limits its performance compared to compiled languages like C++. This divide has led to the creation of Nim, a statically-typed, compiled language with Python-like syntax and features, among other languages.

Syntax

Another very important aspect of creating a programming language is the syntax. At the lowest level, computer processing chips understand machine code - binary instructions made up of patterns of ones and zeroes. Theoretically humans could write programs this way, but it would be incredibly impractical.

Assembly language is a step higher up in the chain, allowing a minimal set of human-readable keywords that can be used to write code. Although some very technical experts can code this way, the vast majority of humans need something better. Higher level programming languages like C, C++, Java, Python, and Javascript provide a robust set of approachable keywords that make programming much more accessible to a broad audience. This reduces the learning curve and allows languages to gain traction grow their communities.

However, even a robust set of keywords can be provided with varying syntax to try and improve developer experiences. For example, some languages use semicolons to separate statements, and parenthesis to encapsulate blocks/scopes. Here is an example of a Java For loop that operates this way:

for ( int x = 0; x < 10; x++ ) {
    ...
}

Other languages like Python simply use newlines and whitespace indentation for this purpose:

sequence = range(10)
for x in sequence:
    ...

Language creators may also have differing opinions on syntax rules that make coding simpler and more intuitive for developers. For example, Python requires very little syntax for variable assignment:

a = 0
b = "Hello"
c = ["C", "h", "e", "e", "s", "e"]
d = { 1: "x", 2: "y", 3: "z" }
e = True

Php on the other hand requires a $ sign before variable names, which is a bit more work for developers:

$a = 0;
$b = "Hello";
$c = ["C", "h", "e", "e", "s", "e"];
$d["1"] = "x"; $d["2"] = "y"; $d["3"] = "z";
$e = true;

Many language creators use their intuition to determine what syntax feels right to them. Differences in this subjective human opinion is part of the reason we have so many languages with unique features and syntax today.

Memory Management

Another important factor in a language's performance is how it manages memory. Compilers and interpreters both need a way to allocate memory to store data into, and free that memory once it is no longer in use. The ways that languages do this typically fall into two categories - manual and automatic.

Some languages, including C++, choose to place more of the burden (and flexibility) of memory management on the developer. This strategy of manual memory management gives the developer complete control of when to allocate and free memory, allowing them to fine-tune a program's performance and memory usage. However, with great power comes great responsibility. Manual memory management requires more forethought from developers, and failure to do so correctly can result in debilitating bugs, memory leaks, and security vulnerabilities.

Languages like Java and Python, on the other hand, internally manage memory allocation. One of the most common schemes for automatic memory management is reference counting. The number of variables referring to an object is tracked over time; once that number reaches 0, the object's memory is freed. This relieves developers from having to worry about memory management and gives them more time to work on other program features, but it adds complexity to the languages inner workings and can lower performance in some implementations.

Since memory management has such a massive impact on a language's performance and ease of use, it alone has been the main differentiator for many new languages. Most older languages, such as C++ and Fortran, were written using manual memory management because techniques for high-performance automatic management did not exist. But now, most new languages use some form of automatic memory management, with ref-counting and garbage collection being the most popular management schemes.

Synchronous vs Asynchronous

The decision to focus on synchronous or asynchronous execution is another difference influenced by modern development practices. In the past, nearly all code was synchronous. In synchronous programs, each line of code is executed in sequential order from start to finish. Of course, conditional statements and loops can lead to differing execution paths, but each instruction will be processed in the order it was coded. If a line of code calls an external resource (e.g. downloading data from a server or calling an API), the program waits until the task is completed before moving on.

On the other hand, asynchronous programming is a newer design pattern that relies upon interruptible processing. Asynchronous programs allow code execution to continue while a particular function waits for external resources or processing. This allows the processor to execute other computational tasks while waiting, reducing the number of processor cycles wasted.

Most languages are synchronous by default, but many popular languages have introduced asynchronous capabilities over the past several years. For example, Python, JavaScript, and Java all support asynchronous functions natively, though JavaScript is much more reliant upon it than the others. The pervasiveness of APIs and other network interactions in modern software has increased the relevance of asynchronous programming in recent years, as it increases efficiency when working with networking and other forms of input/output.

Programming Paradigms

Another set of programming language design decisions are related to programming paradigms to support or favor. In this context, a paradigm is a way of approaching software development problems. No paradigm is objectively better than any other - it all depends on developer preference.

One of the most popular paradigms in modern programming languages is object-oriented programming (OOP). OOP is favored by languages such as Python, Java, C#, and C++. In OOP, developers write classes, which define logical groups of data and code. Programs then operate upon instances of those classes called objects. Here is an example of what this looks like in Java:

public class Animal {

    // An attribute of all animals, usually private to the class
    private String name;

    // Getter method, used to retrieve an attribute of the class
    public String getName() {
        return this.name; // The "this" keyword represents instances created from this class
    }

    // Setter method, used to set an attribute of the class
    public void setName(String name) {
        this.name = name;
    }

    public void eat() {
        System.out.println(this.name + " eats a meal.");
    }

    public void move() {
        System.out.println(this.name + " moves to a new spot.");
    }

}

public class Fish extends Animal { // A fish is an animal, so can inherit the Animal class's attributes and methods

    // Data and code specific to a fish goes in here.
    // Everything defined in the Animal class is available
    // here, since Fish extends that class ("a fish is
    // an animal").

    // New attributes and methods specific to fish can be created
    private Integer numFins = 2;

    public Integer getNumFins() {
        return this.numFins;
    }

    public void setNumFins(Integer numFins) {
        this.numFins = numFins;
    }

    // Parent class attributes and methods can be overridden
    public override void move() {
	System.out.println(this.name + " swims to a new spot.");
    }

}

Another popular paradigm is functional programming (FP), which is favored by C, Haskell, and Scala. In FP, most code is organized into pure functions, or functions that always provide the same output for a given input. Pure functions in FP are typically simple in nature, but can be chained together to produce complex behavior. Since pure functions do not rely on a global state, they can easily be tested in isolation and are modular in nature.

As previously stated, no paradigm is better than any other. In fact, many languages support multiple paradigms, though one is usually preferred and used by most of the community. For example, Python is predominantly object-oriented, but a Python program conceptualized using OOP can often be made using FP as well. However, some languages feature little or no support for alternative paradigms, which can encourage developers to learn multiple languages or even create a new other that supports their desired design philosophy.

Conclusion

The constant increase in the number of programming languages can largely be attributed to compromise. No language can meet every developer's preferences or needs, so new languages are created to fulfill unique and evolving needs. While many new languages never see widespread usage, a few are able to strike a balance between performance, usability, and features, allowing them to grow exponentially and withstand the test of time.

If you're interested in learning the basics of coding and software development, check out our Coding Essentials Guidebook for Developers.

Thanks and happy coding! We hope you enjoyed this article! If you have any questions or comments, feel free to reach out to jacob@initialcommit.io.