These Modern Programming Languages Will Make You Suffer

Diana malewicz anjayani
44 min readDec 9, 2020

What are the pros and cons of a particular programming language? Is X a good language for my task? Googling for “best programming language” will give you a standard list of “Python, Java, JavaScript, C#, C++, PHP” with a vague list of pros and cons. Seeing such articles makes me cringe in pain, their authors must have been outright lazy, inexperienced, while lacking any imagination. Let’s dive deep and find out what really sucks, and what doesn’t.
In this article, I’ll attempt to give an objective and hopefully unbiased overview of popular (and not so popular) modern programming languages, ranked from the worst to the best.
Bear in mind that there’s no one programming language perfectly suited to all possible use cases. Some languages are best suited for Frontend Development, others work best for Backend/API Development, others are great for System Programming.
I’m going to cover two of the most common language families in the world — languages descended from C, and languages descended from ML.
Programming languages are just tools in a developer’s toolbox. And it is important to choose the right tool for the job. I really hope that this guide will help you in choosing the most suitable programming language for your task. Making the right choice might save you months (or even years) of development effort.
What language characteristics really matter?
Image for post
Most other similar articles base their comparisons on factors like popularity and earning potential. Popularity is rarely a good measure, especially in the world of software (although a big community & ecosystem helps). Instead I’ll be taking into account the strengths and weaknesses of a particular language.
I’ll be using a thumbs-up 👍 (i.e. +1 ), a thumbs-down 👎, or an ok 👌 (neither good nor bad) emojis to signify the score of a particular language characteristic.
Now, how will we measure? In other words, what really matters, other than language popularity?
Image for post
Type system
Image for post
Many people swear by type systems. That’s why languages like TypeScript have picked up in popularity in recent years. I tend to agree, type systems eliminate a large number of errors in programs, and make refactoring easier. However, “having” a type system is only one part of the story.
If a language has a type system, then it is also very useful to have type inference. The best type systems are able to infer most of the types, without annotating function signatures explicitly. Unfortunately, most of the programming languages only provide rudimentary type inference.
It is also nice for a type system to support Algebraic Data Types (more on this later).
The most powerful type systems support Higher-Kinded Types, which are one level of abstraction above generics, and allow us to program at an even higher level of abstraction.
We also have to keep in mind that people tend to put too much importance on type systems. There are things that matter far more than static typing, and presence/lack of a type system shouldn’t be the only factor when choosing a language.
Image for post
Learning effort
Image for post
We might have the perfect programming language, but what use is it if onboarding new developers might take months or even years (upfront investment)? On the other side of the spectrum, some programming paradigms take years to become good at.
A good language should be approachable by beginners, and shouldn’t take years to master.
Image for post
Nulls
Image for post
I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language. My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.
- Tony Hoare, the inventor of Null References
Why are null references bad? Null references break type systems. When null is the default value, we can no longer rely on the compiler to check the validity of the code. Any nullable value is a bomb waiting to explode. What if we attempt to use the value that we didn’t think might be null, but it in fact is null? We get a runtime exception.
We have to rely on manual runtime checks to make sure that the value we’re dealing with isn’t null. Even in a statically-typed language, null references take away many benefits of a type system.
Such runtime checks (sometimes called null guards) in reality are workarounds around bad language design. They litter our code with boilerplate. And worst of all, there are no guarantees that we won’t forget to check for null.
In a good language, the lack or presence of a value should be type-checked at compile-time.
Languages that encourage other mechanisms of working with missing data will be ranked higher.
Image for postImage for post
Error handling
Image for postImage for post
Catching exceptions is a bad way to handle errors. Throwing exceptions is fine, but only in exceptional circumstances, when the program has no way to recover, and has to crash. Just like nulls, exceptions break the type system.
When exceptions are used as a primary way of error handling, it is impossible to know whether a function will return an expected value or blow up. Functions throwing exceptions are also impossible to compose.
Obviously, it is not ok for an entire application to crash simply because we couldn’t fetch some data. Yet this is what really happens more often than we’d like to admit.
One option is to manually check for raised exceptions, but this approach is fragile (we may forget to check for an exception), and adds a lot of noise:
Nowadays there are much better mechanisms of error handling, possible errors should be type-checked at compile-time. Languages that do not use exceptions by default will be ranked higher.
Image for postImage for post
Concurrency
Image for postImage for post
We’ve reached the end of Moore’s law, the processors will not get any faster, period. We live in the era of multi-core CPUs, and literally, any modern application has to take advantage of multiple cores.
Unfortunately, most of the programming languages in use today were designed in the era of single-core computing, and simply do not have the features to effectively run on multiple cores.
Libraries that help with concurrency are an after-thought, they simply add band-aids to languages that weren’t initially designed for concurrency. This doesn’t really count as good developer experience. In a modern language, concurrency support has to be built-in (think Go/Erlang/Elixir).
Image for postImage for post
Immutability
Image for postImage for post
I think that large objected-oriented programs struggle with increasing complexity as you build this large object graph of mutable objects. You know, trying to understand and keep in your mind what will happen when you call a method and what will the side effects be.
— Rich Hickey, creator of Clojure.
Programming with immutable values nowadays is becoming more and more popular. Even modern UI libraries like React are intended to be used with immutable values. Languages with first-class support for immutable data values will be ranked higher. Simply because immutability eliminates a whole category of bugs from our code.
What is immutable state? Simply put, it is data that doesn’t change. Just like strings in most programming languages. For example, capitalizing a string will never change the original string — a new string will always be returned instead.
Immutability takes this idea further, and makes sure that nothing is ever changed. A new array will always be returned instead of changing the original one. Updating user’s name? A new user object will be returned with its name updated, while leaving the original one intact.
With immutable state, nothing is shared, therefore we no longer have to worry about the complexity of thread safety. Immutability makes our code easy to parallelize.
Functions that do not mutate(change) any state are called pure, and are significantly easier to test, and to reason about. When working with pure functions, we never have to worry about anything outside of the function. Simply focus on just this one function that you’re working with, while forgetting about everything else. You can probably imagine how much easier development becomes (in comparison to OOP, where an entire graph of objects has to be kept in mind).
Image for postImage for post
Ecosystem/tooling
Image for postImage for post
A language may not be very good, but it may have a large ecosystem which makes it appealing. Having access to good libraries may save one months (or even years) of development effort.
We’ve seen this happen with languages like JavaScript and Python.
Image for postImage for post
Speed
Image for postImage for post
How fast does the language compile? How fast do the programs start? What is the runtime performance? All of these matter, and will be included in the ranking.
Image for postImage for post
Age
Image for postImage for post
Although there are some exceptions, generally, newer languages will be better than older ones. Simply because newer languages learn from the mistakes of their predecessors.
C++
Image for post
Image for post
Let’s begin our rating with the worst of the worst, probably one of the biggest mistakes of computer science, C++. Yes, C++ is not considered a shiny modern programming language. But it is still in wide use today and had to be included in the list.
Language family: C.
Image for post
👎 Language features
Image for post
C++ is a horrible language… And limiting your project to C means that people don’t screw things up with any idiotic “object model” c&@p.
— Linus Torvalds, the creator of Linux.
C++ is bloated with features. It attempts to do everything, while not being good at any particular thing. C++ has goto , pointers, references, OOP, operator overloading, and many other non-productive features.
Why is C++ so bad? In my opinion, the biggest reason is its age. C++ was designed long ago in 1979. At that time the designers lacked the experience, and had no idea what to focus on. The features added might have seemed like a good idea at that time. The language was very popular, which meant that many more features were added to support various use cases (creating an even bigger mess of features).
👎 Speed
C++ is notorious for its slow compilation time. Significantly slower than Java, not as bad as Scala.
The runtime performance, along with startup time is good though.
👎 Ecosystem/tooling
Image for postImage for post
The above tweet makes a good point. The C++ compiler
👎👎 Garbage collection
I had hoped that a garbage collector which could be optionally enabled would be part of C++0x, but there were enough technical problems…
- Bjarne Stroustrup, creator of C++
Garbage collection was never added into C++. Manual memory management is extremely error prone. The developers have to worry about manually releasing and allocating memory. I will never miss the days when I was using non-garbage-collected languages, the innumerous number of bugs that are nowadays easily prevented in garbage-collected languages.
👎 A failed attempt at Object-Oriented Programming
I invented the term Object-Oriented, and I can tell you I did not have C++ in mind.
- Alan Kay, the inventor of object-oriented programming.
Having appeared in the late 60s, OOP was a cool new technology when the work on C++ has started. It’s very unfortunate that C++ made a few crucial mistakes in their implementation of OOP (unlike languages like Smalltalk), which has turned a really good idea into a nightmare.
One good thing about C++, in comparison to Java is that OOP in C++ at least is optional.
👎👎 Learning effort
Image for postImage for post
Mercurial_Rhombus on Reddit
C++ is a complicated low-level language with no automated memory management. Due to its feature bloat, beginners have to spend a lot of time learning the language.
👎 Concurrency
C++ was designed in the era of single-core computing, and has only rudimentary concurrency mechanisms that were added in the past decade.
👎 Error handling
Catching/throwing errors is the preferred error handling mechanism.
👎 Immutability
Has no built-in support for immutable data structures.
👎 Nulls
In C++, all references are nullable.
Image for postImage for post
Verdict
Image for postImage for post
Originally intended to be a better version of C, C++ really failed to deliver.
The best use of C++ is probably system programming. However, given much better and modern alternatives in existence (Rust and Go), C++ shouldn’t even be used for that. I don’t think that C++ has any pros at all, feel free to prove me wrong.
C++, it’s your time to go.
Java
Image for postImage for post
Image for postImage for post
Java is the most distressing thing to happen to computing since MS-DOS.
- Alan Kay, the inventor of object-oriented programming.
Having first appeared in 1995, Java is 16 years younger than C++. Java is a much simpler language, which likely has contributed to its popularity.
Language family: C.
Image for postImage for post
👍 Garbage collection
One of the biggest benefits that Java provides over C++ is garbage collection, which by itself eliminates a large category of bugs.
👍 Ecosystem
Java has been around for a long time, and it has a huge ecosystem for backend development, which significantly reduces development effort.
👎 Object-Oriented language
I will not go too deep into the drawbacks of OOP here, for a more detailed analysis you may read my other article Object-Oriented Programming — The Trillion Dollar Disaster.
Instead I’ll simply quote some of the most prominent people in computer science, to get their opinion on OOP:
I’m sorry that I long ago coined the term “objects” for this topic because it gets many people to focus on the lesser idea. The big idea is messaging.
- Alan Kay, the inventor of OOP
Alan Kay is right, the mainstream OOP languages focus on the wrong thing — classes, and objects, while ignoring messaging. Thankfully, there are modern languages that got this idea right (Erlang/Elixir).
With OOP-inflected programming languages, computer software becomes more verbose, less readable, less descriptive, and harder to modify and maintain.
— Richard Mansfield
Anyone who’s used an OOP language (like Java or C#), and then had experience working in a non-OOP language, can probably relate.
👌 Speed
Java, obviously, runs on top of Java Virtual Machine, which is notorious for its slow startup times. I’ve seen programs running on top of JVM take 30 seconds and longer to start up, which is unacceptable for modern cloud-native programs.
The compilation speed is slow on bigger projects, significantly impacting developer productivity (although nowhere as bad as Scala).
On the upside, the runtime performance of the JVM is really good.
👎 Learning effort
While Java is a rather simple language, its focus on Object-Oriented Programming makes becoming good really hard. One can easily write a simple program. However, knowing how to write reliable and maintainable Object-Oriented code may take well over a decade.
👎 Concurrency
Java was designed in the era of single-core computing, and like C++ has only rudimentary concurrency support.
👎 Nulls
In Java, all references are nullable.
👎 Error handling
Catching/throwing errors is the preferred error handling mechanism.
👎 Immutability
Has no built-in support for immutable data structures.
Image for post
Verdict
Image for post
Java was a decent language when it has appeared. Its too bad that Java (unlike Scala) has always focused exclusively on OOP. The language is very verbose, and suffers a lot from boilerplate code.
The time has come for Java to retire.
C#
Image for post
Image for post
Fundamentally, there’s very little difference between C# and Java (since the early versions of C# were actually a Microsoft implementation of Java).
C# shares most of its cons with Java. Having first appeared in 2000, C# is 5 years younger than Java, and has learned a few things from Java’s mistakes.
Language family: C.
Image for post
👌 Syntax
C# syntax has always been a little ahead of Java. C# suffers less from boilerplate code than Java. Although being an OOP language, C# is more on the verbose side. It’s good to see C# syntax being improved with every release, with the addition of features like expression-bodied function members, pattern matching, tuples, and others.
👎 Object-Oriented language
Just like Java, C# focuses mostly on OOP. Once again, I’m not going to spend too much time here trying to convince you of the drawbacks of OOP, I’ll simply quote a few more prominent people in computer science.
I think the lack of reusability comes in object-oriented languages, not in functional languages. Because the problem with object-oriented languages is they’ve got all this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and the entire jungle.
— Joe Armstrong, creator of Erlang
I have to agree with Joe Armstrong, reusing object-oriented code is very difficult, in comparison with functional (or even imperative) code.
Object oriented programs are offered as alternatives to correct ones…
— Edsger W. Dijkstra, pioneer of computer science
Having worked with both OOP and non-OOP languages throughout my career, I have to agree that OOP code is much harder to get right, in comparison to non-OOP code.
👎 Multi-paradigm?
C# claims to be a multi-paradigm language. In particular, C# claims to support functional programming. I must disagree, having support for first-class functions is simply not enough for a language to be called functional.
What functional features should a language have? At the very least, built-in support for immutable data structures, pattern matching, pipe operator for function composition, Algebraic Datatypes.
👎 Concurrency
C# was created in the era of single-core computing, and like Java has only rudimentary concurrency support.
👎 Nulls
In C#, all references are nullable.
👎 Error handling
Catching/throwing errors is the preferred error handling mechanism.
👎 Immutability
Has no built-in support for immutable data structures.
Image for postImage for post
Verdict
Image for postImage for post
I’ve spent a large chunk of my career working with C#, and was always mostly frustrated with the language. Just like with Java, I’d recommend looking for more modern alternatives. It is the same Java under the hood, with a little more modern syntax.
Unfortunately, there’s nothing “sharp” about C#.
Python
Image for postImage for post
Image for postImage for post
Having first appeared in 1991, Python is an old language. Along with JavaScript, Python is one of the most popular languages in the world.
Language family: C.
Image for postImage for post
👍 Ecosystem
Python has a library almost for anything. Unlike JavaScript, Python can’t be used for Frontend Web Development, however Python easily makes up with a huge number of data science libraries.
👍 Learning effort
Python is a very simple language which can be picked up by beginners in a couple of weeks.
👎 Type system
Python is dynamically typed, there’s not much more to say about the type system.
👎 Speed
Python is an interpreted language and is notorious for being one of the slowest programming languages, in terms of runtime performance. Using Cython instead of plain Python may be a good solution where runtime performance is critical.
Python is also pretty slow to start up, in comparison to native languages.
👎 Tooling
Having used Python along with other modern languages, it’s hard not to be disappointed with Python’s dependency management. There’s pip, pipenv, virtualenv, pip freeze and others. In comparison, NPM in JavaScript is the only tool you’ll ever need.
👎 Concurrency
Python was not created with concurrency in mind, has only rudimentary concurrency support.
👎 Nulls
In Python, all references are nullable.
👎 Error handling
Catching/throwing errors is the preferred error handling mechanism.
👎 Immutability
Has no built-in support for immutable data structures.
Image for postImage for post
Verdict
Image for postImage for post
It is really unfortunate that Python has no proper support for functional programming. Functional Programming is suited extremely well for problems that data science is trying to solve. Even for very pythonic tasks like web scraping, functional languages (like Elixir) are a much better fit.
I don’t recommend using Python for large projects, the language was not built with serious software engineering in mind.
Python shouldn’t be used for anything other than data science, when no other alternatives are available. Julia seems to be a good modern alternative to Python in the field of data science, although its ecosystem is not nearly as mature as Python’s.
Rust
Image for postImage for post
Image for postImage for post
Rust is a modern low-level language, initially designed as a replacement for C++.
Language family: C.
Image for postImage for post
👍 Speed
Rust was designed from the ground up to be fast. Compilation of Rust programs takes longer than compilation of Go programs. The runtime performance of Rust programs is a little faster than Go.
👍 Nulls
The first language on our list with a modern null alternative! Rust doesn’t have a null or nil value, and Rust developers use the Option Pattern instead.
👍 Error handling
Rust takes a modern functional approach to error handling, and uses a dedicated Result type to signify an operation that might fail. It is very similar to the Option above, however the None case now also has a value.
👎 Memory management
Rust is the only modern language on our list with no garbage collection. This forces the developers to think about low-level memory management, and makes developer productivity suffer.
👎 Concurrency
Due to the lack of garbage collection, concurrency is rather hard in Rust. Developers have to worry about things like boxing and pinning, which typically are done automatically in a garbage-collected language.
👎 Immutability
Rust has no built-in support for immutable data structures.
👎 Low-level language
Being a low-level language, developer productivity in Rust can’t be as high as in other higher-level languages. This also makes the learning effort significantly harder.
Image for postImage for post
Verdict
Image for postImage for post
Rust is a good fit for system programming. Although more complex than Go, it provides a powerful type system. Rust provides a modern alternative to nulls, and a modern way for handling errors.
Why is Rust still ranked below TypeScript and JavaScript? It is a low-level language designed for system programming. Rust is not a very good fit for Backend/Web API development. It lacks garbage collection, and has no built-in support for immutability.
TypeScript
Image for postImage for post
Image for postImage for post
TypeScript is a compile-to-js language. Its main goal is to make a “better JavaScript” by adding static typing to JavaScript. Just like JavaScript, TypeScript is being used for both frontend and backend development.
TypeScript was designed by Anders Hejlsberg, the same person, who has designed C#. TypeScript code feels very C-sharpy, and fundamentally can be thought of as C# for the browser.
Language family: C.
Image for postImage for post
👎 Superset of JavaScript
Yes, being a superset of JavaScript has helped a lot with the adoption of TypeScript. After all, a lot of people already know JavaScript.
However, being a superset of JavaScript is more of a drawback. This means that TypeScript carries all of the JavaScript baggage. It is limited by all the bad design decisions made in JavaScript.
For example, how many of you like the this keyword? Probably nobody, yet TypeScript has deliberately decided to keep that in.
How about the type system acting really weird at times?
[] == ![]; // -> true
NaN === NaN; // -> false
In other words, TypeScript shares all of the drawbacks with JavaScript. Being a superset of a bad language can’t turn out to be good.
👍 Ecosystem
TypeScript has access to the entire JavaScript ecosystem, which is enormous. A huge benefit. Node Package Manager is a real pleasure to work with, especially in comparison to other languages, like Python.
The drawback is that not all JavaScript libraries have usable TypeScript declarations. Rambda, immutable.
👌 Type system
I’m not too excited about the type system in TypeScript, it is ok.
On the bright side, it even supports Algebraic Data Types (Discriminated Unions):
Let’s take a look at the same piece of code implemented in ReasonML:
The TypeScript syntax is not as good as in functional languages. Discriminated Unions were added in TypeScript 2.0 as an afterthought. In the switch, we’re matching on strings which is error-prone, and the compiler won’t warn us if we miss a case.
TypeScript provides only rudimentary type inference. Also, when using TypeScript, you will find using any more often than you’d like to.
👌 Nulls
TypeScript 2.0 has added support for non-nullable types, it can optionally be enabled using the — strictNullChecks compiler flag. But. Programming with non-nullable types is not the default, and is not considered to be idiomatic in TypeScript.
👎 Error handling
In TypeScript, errors are handled by throwing/catching exceptions.
👎 New JS features
JavaScript gets support for cool new features sooner than TypeScript. Even experimental features can be enabled in JavaScript with the use of Babel, which can’t be done for TypeScript.
👎 Immutability
Dealing with immutable data structures in TypeScript is significantly worse than in JavaScript. While JavaScript developers can use libraries that help with immutability, TypeScript developers typically have to rely on the native array/object spread operators (copy-on-write):
Unfortunately, the native spread operator doesn’t perform a deep copy, and manually spreading deep objects is cumbersome. Copying large arrays/objects is also not good for performance.
The readonly keyword in TypeScript is nice, it makes properties immutable. However it is a long way from having support proper immutable data structures.
JavaScript has good libraries for working with immutable data (like Rambda/Immutable.js). However, getting such libraries to work with the TypeScript type system can be very tricky.
👎 TypeScript & React — a match made in hell?
Dealing with immutable data in JavaScript[and TypeScript] is more difficult than in languages designed for it, like Clojure.
- Straight from React Documentation
Continuing from the previous drawback, if you’re doing frontend web development, then the chances are that you’re using React.
React was not made for TypeScript. React initially was made for a functional language (more on this later). There’s a conflict between programming paradigms — TypeScript is OOP-first, while React is functional-first.
React expects its props (i.e. function arguments) to be immutable, while TypeScript has no proper built-in support for immutable data structures.
The only real benefit that TypeScript provides over JavaScript for React development is not having to worry about PropTypes.
Image for postImage for post
TypeScript or Hypescript?
Image for postImage for post
Is TypeScript just a hype? That’s up to you to decide. I think it is. Its biggest benefit is having access to the entire JavaScript ecosystem.
Why is HypeScript so popular then? The same reason that Java and C# became popular for — being backed by multi-billion corporations with huge marketing budgets.
Image for postImage for post
Verdict
Image for postImage for post
Although TypeScript is generally thought to be a “better JavaScript”, I’m rating it lower than JavaScript. The benefits it provides over JavaScript are overrated, especially for frontend web development with React.
TypeScript has really failed to deliver by keeping all of the bad parts of JavaScript, effectively inheriting decades of bad design decisions made in JavaScript.
Go
Image for postImage for post
Image for postImage for post
Go was designed to help with programming productivity in the era of multicore processors, and large codebases. The designers of Go were primarily motivated by their mutual dislike of C++, which was widely used at Google at that time.
Language family: C.
Image for postImage for post
👍 Concurrency
Concurrency is Go’s “killer” feature, Go was built from ground-up for concurrency. Just like Erlang/Elixir, Go follows the mailbox model of concurrency. Unfortunately, goroutines in Go do not provide the same fault tolerance features that Erlang/Elixir processes have. In other words, an exception in a goroutine will bring down the entire program, whereas an exception in an Elixir process will bring down just that one process.
👍 👍 Speed
One of the major reasons Google has created Go is compilation speed. There’s even a joke that “Google has created Go while waiting for their C++ code to compile”.
Go is a very fast language. Startup time of Go programs is very fast. Go compiles to native code, therefore its runtime speed is also amazing.
👍 Learning effort
Go is a simple language, which can probably be learned in about a month by someone with previous programming experience.
👍 Error handling
Go doesn’t support exceptions, instead Go makes the developer handle possible errors explicitly. Similarly to Rust, Go returns two values — the result of a call, and a potential error. If everything went well, then the error will be nil:

👍 No Object-Oriented Programming
While some may disagree, I personally think that the lack of OOP features is a big advantage.
To repeat Linus Torvalds:
C++ is a horrible [object-oriented] language… And limiting your project to C means that people don’t screw things up with any idiotic “object model” c&@p.
— Linus Torvalds, the creator of Linux
Linus Torvalds is widely known for his open criticism of C++ and OOP. One thing he was 100% right about is limiting programmers in the choices they can make. In fact, the fewer choices programmers have, the more resilient their code becomes.
In my opinion, Go intentionally omitted many OOP features, to not repeat the mistakes of C++.
👌 Ecosystem
Some of the standard libraries are really dumb. Large parts of it are inconsistent with Go’s own philosophy of returning out-of-band errors (e.g. they return a value like -1 for an index instead of (int, error)), and others rely on global state, such as flag and net/http.
There’s a lack of standardization in Go’s standard libraries. For example, some libraries in case of an error return (int, error) , others return values like -1 , while others rely on global state such as flag .
The ecosystem is nowhere as big as JavaScript.
👎 Type System
Image for post
Pretty much every modern programming language has generics in one form or another (including the dreaded C#/Java, and even C++ has templates). Generics allow the developer to reuse function implementations for different types. Without generics you’d have to implement the add function separately for integers, for doubles, and for floats, resulting in a lot of code duplication. In other words, the lack of generics in Go results in a large amount of duplicate code. As some people say, “Go” is short for “Go write some boilerplate”
👎 Nulls
It’s unfortunate that Go has included nulls into the language when safer alternatives have been available for decades.
👎 Immutability
Go has no built-in support for immutable data structures.
Image for postImage for post
Verdict
Image for postImage for post
Go is not a good language. It’s not bad; it’s just not good. We have to be careful using languages that aren’t good, because if we’re not careful, we might end up stuck using them for the next 20 years.
- Will Yager in Why Go Is No Good
If you’re not Google and don’t have use cases similar to Google’s, then Go probably is not a good choice. Go is a simple language best suited for system programming. Go is not a great option for API development (simply because there are much better options available, more on that later).
I think that overall Go is a better choice than Rust (albeit with a weaker type system). It is a simple language that is really fast, is easy to learn, and has great concurrency features. And yes, Go successfully accomplished its design goal of being a “better C++”.
Image for postImage for post
Best System Language Award
Image for postImage for post
The Best System Language award goes to Go. Undoubtedly, Go is the perfect choice for system programming. Go is a low-level language, and its great fit for this field is confirmed by a large number of successful projects built with Go, like Kubernetes, Docker, and Terraform.
JavaScript
Image for postImage for post
Image for postImage for post
Being the most popular programming language in the world, JavaScript doesn’t need introduction.
And yes, this is not a mistake. JavaScript really is ranked above Rust, TypeScript, and Go. Let’s find out why.
Language family: C.
Image for postImage for post
👍 👍 Ecosystem
The biggest benefit of JavaScript is its ecosystem. JavaScript is being used for everything you can think of — frontent/backend web development, CLI programming, data science, and even machine learning. JavaScript probably has a library for everything you can think of.
👍 Learning effort
JavaScript (along with Python) is one of the easiest programming languages to learn. One can become productive in JavaScript in a couple of weeks.
👎 Type system
Just like Python, JavaScript is dynamically typed, there’s not much more to say. JavaScript’s type system sometimes can be very weird:
👌 Immutability
As already noted in the TypeScript section, the spread operator can be bad for performance and doesn’t even perform a deep copy when copying objects. JavaScript lacks built-in support for immutable data structures, although there are libraries that can help with that (Ramda/Immutable.js).
👎 React wasn’t made for JavaScript
Using PropTypes is a must when using React in JavaScript. However, this also means that the PropTypes have to be maintained, which can become a nightmare.
Also, subtle performance issues can be introduced if you’re not careful:
Such innocent-looking code can become a performance nightmare, since in JavaScript [] != [] . The above code will cause the HugeList to re-render on every single update, even though the options value hasn’t changed. Such issues can compound, until the UI eventually becomes impossible to use.
👎 this keyword
The biggest anti-feature of JavaScript probably is the this keyword. Its behavior is consistently inconsistent. It is finicky and can mean completely different things in different contexts. Its behavior even depends on who has called a given function. Using this keyword oftentimes results in subtle and weird bugs that can be hard to debug.
👌 Concurrency
JavaScript supports single-threaded concurrency using an event loop. This eliminates the need for thread synchronization mechanisms (like locking). Although JavaScript was not built with concurrency in mind, working with concurrent code is much easier, in comparison to most other languages.
👍 New JS features
JavaScript gets support for cool new features sooner than TypeScript. Even experimental features can be enabled in JavaScript with the use of Babel.
👎 Error handling
Catching/throwing errors is the preferred error handling mechanism.
Image for postImage for post
Verdict
Image for postImage for post
JavaScript is not a well-designed language. The initial version of JavaScript was put together in 10 days (although the future releases have addressed many of its shortcomings).
Despite its shortcomings, JavaScript is a decent choice for fullstack web development. With proper discipline and linting, JavaScript can be a good language.
Functional Programming == Peace of Mind
Image for postImage for post
Photo by Ante Hamersmit on Unsplash
Let’s make a small detour before we continue our ranking. Why bother with functional programming? Functional programming gives us peace of mind.
Yes, functional programming may sound scary, but actually there’s nothing to be afraid of. Simply put, functional languages made many right design decisions, where others made wrong decisions. In most cases, functional languages will have just the right features: powerful type system with Algebraic Data Type support, no nulls, no exceptions for error handling, built-in immutable data structures, pattern matching, function composition operators.
What common strengths do functional programming languages have that puts them so high up in our ranking?
Image for postImage for post
Programming with Pure Functions
Image for postImage for post
Unlike imperative(mainstream languages), functional programming languages encourage programming with pure functions.
What is a pure function? The idea is very simple — a pure function will always return the same output, given the same input. For example, 2 + 2 will always return 4, which means that the addition operator + is a pure function.
Pure functions are not allowed to interact with the outside world (making API calls, or even writing to the console). It is not even allowed to change state. It is the exact opposite of the approach taken by OOP, where any method can freely mutate the state of other objects.
One can easily tell pure functions from impure functions — does the function take no arguments, or return no value? Then it is an impure function.
Here’s an example of a few impure functions:
And a couple of pure functions:
Such approach may seem to be very limiting, and can take some time getting used to. It certainly was confusing at first for me!
What are the benefits of pure functions? They are very easy to test (no need for mocks and stubs). Reasoning about pure functions is easy — unlike in OOP, there’s no need to keep in mind the entire application state. You only need to worry about the current function that you’re working on.
Pure functions can be composed easily. Pure functions are great for concurrency, since no state is shared between functions. Refactoring pure functions is pure joy — just copy and paste, no need for complex IDE tooling.
Simply put, pure functions bring the joy back into programming.
Functional programming encourages the use of pure functions — it is good when more than 90% of the codebase consists of pure functions. Some languages take this to an extreme, and disallow non-pure functions altogether (which is not always a great idea).
Image for postImage for post
Immutable data structures
Image for postImage for post
All of the functional languages below have built-in support for immutable data structures. The data structures are also persistent. This simply means that whenever a change is being made, we don’t have to create a deep copy of the entire structure. Imaging copying over an array of over 100,000 items over and over again, this must be slow right?
Instead of creating a copy, persistent data structures simply reuse a reference to the older data structure, while adding in the desired changes.
Image for postImage for post
Algebraic Data Types
Image for postImage for post
ADTs are a powerful way of modeling application state. One can think of them as Enums on steroids. We specify the possible “subtypes” that our type can be composed of, along with its constructor parameters:
The type “shape” above can be either a Square, a Rectangle, or a Circle. The Square constructor takes a single int parameter (width), Rectangle takes two intparameters (width and height), and Circle takes a single int parameter (its radius).
Here’s similar code, implemented in Java:
I don’t know about you, but I’d definitely go with the former version, using ADTs in a functional language.
Image for postImage for post
Pattern matching
Image for postImage for post
All of the functional languages have great support for pattern matching. In general, pattern matching allows one to write very expressive code.
Here’s an example of pattern matching on an option(bool) type:
Same code, without pattern matching:
No doubt, the pattern matching version is much more expressive and clean.
Pattern matching also provides compile-time exhaustiveness guarantees, meaning that we won’t forget to check for a possible case. No such guarantees are given in non-functional languages.
Image for postImage for post
Nulls
Image for postImage for post
Functional programming languages generally avoid using the null reference. Instead, the Option pattern is used (similar to Rust):
Image for postImage for post
Error handling
Image for postImage for post
The use of exceptions is generally discouraged in functional languages. Instead, the Result pattern is used (once again, similar to Rust):
For a great introduction into functional ways of error handling, make sure to read Composable Error Handling in OCaml.
Image for postImage for post
Pipe forward operator
Image for postImage for post
Without the pipe forward operator, function calls tend to become deeply nested, which makes them less readable:
Functional languages have a special pipe operator that makes this task much easier:
Image for postImage for post
Image for postImage for post
Haskell
Haskell can rightfully be called the “mother” of all Functional Programming languages. Haskell is 30 years old, even older than Java. Many of the best ideas in functional programming, have originated in Haskell.
Language family: ML.
Image for postImage for post
👍 👍 Type system
There’s no type system more powerful than Haskell. Obviously, Haskell supports algebraic data types, but it also supports typeclasses. Its type checker is able to infer pretty much anything.
👎👎 Learning effort
Oh boy! It’s not a secret, that in order to use Haskell productively, one has to be well-versed in Category Theory first (and I’m not kidding). Whereas OOP requires years of experience to write decent code, Haskell requires a very significant investment into learning upfront, before one can even be productive.
Writing even a simple “hello world” program in Haskell requires understanding of Monads (IO Monads in particular).
👎👎 Community
The Haskell community, in my experience, is far more academic. A recent post to the Haskell libraries mailing list began with:
“It was pointed out to me in a private communication that the tuple function \x->(x,x) is actually a special case of a diagonalization for biapplicative and some related structures monadicially.”
It received 39 pretty enthusiast replies.
- momentoftop on Hacker News
The quote above sums up the Haskell community pretty well. The Haskell community is more interested in academic discussions (and category theory) rather than in solving real-world problems.
👎 Functional purity
As we have already learned, pure functions are amazing. Side effects (e.g. interacting with the outside world, including mutating state) are a cause of a large number of errors in programs. Being a pure functional language, Haskell disallows them altogether. This means that functions can never change any values, and aren’t even allowed to interact with the outside world (even things like logging aren’t technically allowed).
Of course, Haskell provides workarounds to interact with the outside world. How does it work you may ask? We provide a set of instructions (IO Monad). Such instructions may say: read keyboard input, then use that input in some function, and then print the result to the console. The language runtime then takes such instructions, and executes them for us. We never execute code that interacts with the outside world directly.
Avoid success at all costs!
- Haskell’s unofficial motto.
In practice, such focus on functional purity significantly increases the number of abstractions, which increases complexity, and consequently decreases developer productivity.
👍 Nulls
Just like Rust, Haskell has no null reference. It uses the Option pattern to signify a value that may not be present.
👍 Error handling
While some functions may throw errors, idiomatic Haskell code uses a pattern similar to the Result type in Rust.
👍 Immutability
Haskell has first-class support for immutable data structures.
👍 Pattern matching
Haskell has great pattern matching support.
👎 Ecosystem
The standard library is a mess, especially the default prelude (the core library). By default, Haskell uses functions that throw exceptions instead of returning option values (the gold standard of functional programming). To add to the mess, Haskell has two package managers — Cabal and Stack.
Image for postImage for post
Verdict
Image for postImage for post
Hardcore functional programming is not going to ever become mainstream — it requires deeply understanding many highly abstract concepts.
- David Bryant Copeland in Four Better Rules for Software Design
I really wanted to like Haskell. Unfortunately, Haskell will likely forever be confined to academic circles. Is Haskell the worst of functional programming languages? It’s up to you to decide, I think it is.
OCaml
Image for postImage for post
Image for postImage for post
OCaml is a functional programming language. OCaml stands for Object Caml, however, the irony is that you will rarely find anyone using objects in OCaml.
OCaml is almost as old as Java, and the “Objects” part of the name probably reflects the “Objects” hype of that era. OCaml simply picks up where Caml left off.
Language family: ML.
Image for postImage for post
👍 👍 Type system
The type system of OCaml is almost as good as Haskell. The biggest drawback is the lack of typeclasses, but it supports functors (higher-order modules).
OCaml is statically typed, its type inference is almost as good as Haskell.
👎👎 Ecosystem
OCaml community is small, meaning that you won’t find high-quality libraries for common use cases. For example, OCaml lacks a decent web framework.
The documentation for OCaml libraries is quite bad, in comparison to other languages.
👎 Tooling
The tooling is a mess. There’re three package managers — Opam, Dune, and Esy.
OCaml is known for pretty bad compiler error messages. While not a deal breaker, this is somewhat frustrating, and can affect developer productivity.
👎 Learning resources
The go-to book for learning OCaml is Real World OCaml. The book hasn’t been updated since 2013, and many of the examples are outdated. Following the book is impossible with modern tooling.
The language tutorials, generally, are extremely poor (in comparison to other languages). They mostly are lecture notes from academic courses.
👎 Concurrency
“Multicore is coming Any Day Now™️” — sums up the story with concurrency in OCaml. OCaml developers have been waiting for proper multicore support for years, and it doesn’t seem like it is going to be added to the language in the near future. OCaml seems to be the only functional language that lacks proper multicore support.
👍 Nulls
OCaml has no null reference, and uses the Option pattern to signify a value that may not be present.
👍 Error handling
Idiomatic OCaml code uses the Result type pattern.
👍 Immutability
OCaml has first-class support for immutable data structures.
👍 Pattern matching
OCaml has great pattern matching support.
Image for postImage for post
Verdict
Image for postImage for post
OCaml is a good functional language. Its main drawbacks are poor concurrency support, and a small community (hence a small ecosystem, and lack of learning resources).
Given its shortcomings, I would not recommend using OCaml in production.
Leaving OCaml
Scala
Image for postImage for post
Image for postImage for post
Scala is one of the few truly multi-paradigm languages, having really good support for both Object-Oriented and Functional programming.
Language family: C.
Image for postImage for post
👍 Ecosystem
Scala runs on top of the Java Virtual Machine, which means that it has access to the huge ecosystem of Java libraries. This is a true boon for developer productivity, when working on the backend.
👍 Type System
Scala probably is the only typed functional language with an unsound type system that also lacks proper type inference. The type system in Scala is not as good as in other functional languages.
On the bright side, Scala supports Higher-Kinded Types, and typeclasses.
Despite its shortcomings, the type system still is very good, hence a thumbs up.
👎 Conciseness/readability
While Scala code is very concise, especially in comparison with Java, the code isn’t very readable.
Scala is one of the few functional languages that actually belong to the C-family of programming languages. C-family languages were intended to be used with imperative programming, while ML-family languages were intended to be used with functional programming. Therefore functional programming in C-like syntax in Scala may feel weird at times.
There’s no proper syntax for Algebraic Data Types in Scala, which adversely affect readability:
ADTs in ReasonML:
ADTs in an ML language is a clear winner in terms of readability.
👎 👎 Speed
Scala probably is one of the worst programming languages out there in terms of compilation speed. A simple “hello world” program might take up to 10 seconds to compile on older hardware. Scala compiler is not concurrent (compiles code using a single core), which doesn’t help with compilation speed.
Scala runs on top of Java Virtual Machine, which means that programs will take longer to start up.
👎 Learning effort
Scala has a lot of features, which makes it harder to learn. Just like C++, the language is bloated with features.
Scala is one of the most difficult functional languages (second only to Haskell). In fact, its poor learnability is number one deciding factor for companies when leaving Scala.
👍 Immutability
Scala has first-class support for immutable data structures (using case classes).
👌 Nulls
On the downside, Scala supports null references. On the upside, the idiomatic way of working with potentially missing values is using the Option pattern (just like other functional languages).
👍 Error handling
Just like in other functional languages, it is idiomatic is Scala to use the Result pattern for error handling.
👌 Concurrency
Scala runs on top of the JVM, which wasn’t really built for concurrency. On the upside, the Akka toolkit is very mature, and provides Erlang-like concurrency on the JVM.
👍 Pattern matching
Scala has great pattern matching support.
Image for postImage for post
Verdict
Image for postImage for post
I really really wanted to like Scala, but I just couldn’t. Scala attempts to do too much. Its designers had to make many tradeoffs in order to support both OOP and FP. As the Russian proverb goes — “The Person Who Chases Two Rabbits Catches Neither”.
Elm
Image for postImage for post
Image for postImage for post
Elm is a functional compile-to-js language used primarily for frontend web development.
What makes Elm unique is its promise of no runtime exceptions, ever. Applications written in Elm are very robust.
Language family: ML.
Image for postImage for post
👍 Very nice error messages
The Elm compiler provides some of the nicest error messages I’ve ever seen, which makes the language much more approachable even to complete beginners.
👍 Error handling
Elm has no runtime errors, the language doesn’t support exceptions, period. Elm is a pure functional language with no runtime exceptions. This means that if your codebase is 100% Elm, then you will never see runtime errors. The only time when you will encounter runtime errors with Elm is when interacting with outside JavaScript code.
How does Elm handle errors? Just like many other functional languages, using Resultdata type.
👎 Functional purity
Just like Haskell, Elm is a pure functional language.
Does Elm make you more productive by eliminating all runtime exceptions, or does it make you less productive by forcing functional purity everywhere? In my experience, any significant refactoring in Elm is a nightmare, because of the significant amount of “plumbing” involved.
Decide for yourself, but I’ll give this characteristic of Elm a thumbs down.
👎 Overly opinionated
Image for postImage for post
Quigglez on Reddit
The Elm is an opinionated language. To the point that using tabs is considered a syntactic error.
Elm’s focus on “no errors ever” is killing the language. The latest version (0.19) has introduced a breaking change, which makes interop with JavaScript libraries next to impossible. The intention, of course, is for people to write their own libraries in Elm to help the ecosystem grow. However, few companies have the resources to reimplement everything in Elm. This made many people to leave Elm for good.
The designer of Elm seems to be too focused on functional purity, taking the “no errors ever” idea to the extreme.
👎 No React
Elm makes use of its own Virtual DOM, and unlike languages like ReasonML, it doesn’t use React. This means that the developers have no access to the vast ecosystem of libraries and components made for React.
👎 👎 Language development
Sadly, it’s been over a year since a new version of Elm has been released (0.19.1). There’s zero transparency on the development process, there’s no way for anyone to contribute to the development. With every major release Elm has introduced breaking changes which made the language impossible to use for some. We haven’t really heard anything from its creator for over a year now. We don’t even know whether or not he’s still working full-time on Elm. The language might actually be dead by now.
👍 Pattern matching
Elm has great pattern matching support.
👍 Immutability
Elm has first-class support for immutable data structures.
👍 Nulls
Elm doesn’t support nullable references, and just like other functional languages makes use of the Option pattern instead.
Image for post
Verdict
Image for post
Elm is an excellent language. Unfortunately, it doesn’t seem to have a future. But it can be a great way to get into Functional Programming.
The Biggest Problem with Elm by Charles Scalfani
Why I’m leaving Elm by Luke Plant
F#
Image for post
Image for post
F# can be summed up as OCaml for .NET. Its syntax is very similar to OCaml, with a few minor differences. Having first appeared in 2005, F# is a very mature language with great tooling and a rich ecosystem.
Language family: ML.
Image for postImage for post
👍 👍 Type system
The only type system drawback is the lack of Higher-Kinded Types. Still the type system is very solid, the compiler is able to infer pretty much anything. F# has proper support for ADTs.
👍 Functional, but not pure
Unlike Haskell/Elm, F# is very pragmatic, and does not enforce function purity.
👍 Learning resources
F# has some really good learning resources, probably on par with Elixir.
👍 Learning effort
F# is one of the easiest functional languages to pick up.
👌 Ecosystem
F# community is rather small, and unlike languages like Elixir, it simply doesn’t have the same great libraries.
👍 C# interop
On the upside, F# has access to the entire .NET/C# ecosystem. Interop with existing C# code is really good.
👌 Concurrency
F# runs on top of CLR, which doesn’t have the same superb concurrency support that Elixir enjoys from the Erlang VM (more on this later).
👍 Nulls
The null value is not normally used in F# code. It uses the Option pattern to signify a value that may not be present.
👍 Error handling
Idiomatic F# code uses the Result pattern for error handling.
👍 Immutability
F# has first-class support for immutable data structures.
👍 Pattern matching
F# has great pattern matching support.
Image for postImage for post
Verdict
Image for postImage for post
F# is a very solid programming language with a really good type system. It is almost as good as Elixir for Web API development (more on this later). However, the problem with F# is not what it has, but what it doesn’t have. To draw comparison with Elixir, its concurrency features, rich ecosystem, and amazing community outweigh any static typing benefits that F# provides.
Dark’s new backend will be in F#
Image for postImage for post
Awards
Image for postImage for post
F# receives two awards.
F# gets the Best Language for Fintech award. It’s no secret that finances is one of the biggest applications of F#.
F# also gets the Best Language for Enterprise Software award. Its rich type system allows for the modeling of complex business logic. Domain Modeling Made Functional is a highly recommended book to read.
ReasonML
Image for postImage for post
Image for postImage for post
ReasonML is a functional compile-to-js language used primarily for frontend web development.
ReasonML is not a new language, it is a new syntax for OCaml (an old and tried programming language). ReasonML is backed by Facebook.
By leveraging the JavaScript ecosystem, ReasonML doesn’t suffer from the same drawbacks as OCaml.
Language family: ML.
Image for postImage for post
👍 Not a superset of JavaScript
ReasonML’s syntax is similar to JavaScript, which makes it much more approachable to anyone with JavaScript experience. However, unlike TypeScript, ReasonML doesn’t even attempt to be a superset of JavaScript (a good thing as we’ve already learned). Unlike TypeScript, ReasonML didn’t have to inherit decades of bad design decisions made by JavaScript.
👍 Learning effort
Since ReasonML doesn’t even attempt to be a superset of JavaScript, it makes the language much simpler than JavaScript. Somebody with functional programming experience in JavaScript can pick up ReasonML in about a week.
ReasonML truly is one of the simplest programming languages out there.
👍 Functional, but not pure
Unlike Elm, ReasonML doesn’t even attempt to be a pure functional language, and has no goal of “no runtime errors ever”. This means that ReasonML is very pragmatic, is focused on developer productivity, and achieving results fast.
👍 👍 Type System
ReasonML really is OCaml, which means that its type system is almost as good as Haskell. The biggest drawback is the lack of typeclasses, but it supports functors (higher-order modules).
ReasonML is statically typed, its type inference is almost as good as Haskell.
👍 👍 Ecosystem
Just like TypeScript, ReasonML has access to the entire JavaScript ecosystem.
👍 JavaScript/TypeScript interop
ReasonML compiles to plain JavaScript. Therefore it is possible to use both ReasonML and JavaScript/TypeScript in the same project.
👍 ReasonML & React — a match made in heaven
If you’re doing frontend web development, then the chances are that you’re using React. Did you know, that React initially was written in OCaml, and only then was ported to JavaScript to help with adoption?
Since ReasonML is statically-typed, there’s no need to worry about PropTypes.
Remember the innocent-looking example from the section on JavaScript that can cause performance disasters?
ReasonML has proper support for immutable data structures, and such code will not create performance issues:
Unlike JavaScript, with ReasonML nothing gets unnecessarily re-rendered, great React performance out-of-the-box!
👎 Tooling
ReasonML isn’t nearly as mature as the alternatives like TypeScript, and there might be some issues with the tooling. For example, the officially recommended VSCode extension reason-language-server is currently broken, however other alternatives exist.
ReasonML uses the OCaml compiler under the hood, and OCaml is known for pretty bad compiler error messages. While not a deal breaker, this is somewhat frustrating, and can affect developer productivity.
I expect the tooling to improve as the language becomes more mature.
👍 Nulls
ReasonML has no null reference, and uses the Option pattern to signify a value that may not be present.
👍 Immutability
ReasonML has first-class support for immutable data structures.
👍 Pattern matching
ReasonML has great pattern matching support.
Image for postImage for post
Verdict
Image for postImage for post
ReasonML probably is what TypeScript has always aimed to be, but failed. ReasonML adds static typing to JavaScript, while removing all of the bad features (and adding in modern features that truly matter).
Image for postImage for post
Best Frontend Language Award
Image for postImage for post
The Best Frontend Language award goes to ReasonML. Undoubtedly, ReasonML is the best option for frontend web development.
Elixir
Image for post
Image for post
Elixir probably is the most popular functional programming language in the world. Just like ReasonML, Elixir is not really a new language. Instead, Elixir builds upon more than three decades of Erlang’s success.
Elixir is Go’s functional cousin. Just like Go, Elixir was designed from the ground up for concurrency to take advantage of multiple processor cores.
Unlike some other functional languages, Elixir is very pragmatic. It is focused on getting results. You will not find long academic discussions in the Elixir community. The Elixir Forum is full of solutions to actual real-world problems, and the community is very friendly to beginners.
Language family: ML.
Image for post
👍 👍 Ecosystem
What really makes Elixir shine is its ecosystem. In most other languages there’s the language, and then there’s the ecosystem, two separate things. In Elixir, the core frameworks in the ecosystem are being developed by the core Elixir team. José Valim, the creator of Elixir is also the main contributor in Phoenix and Ecto — the super cool libraries in the Elixir ecosystem.
In most other languages there’re multiple different libraries focused on the same task — many different web servers, many different ORMs, etc. In Elixir, the development efforts are really focused on the core few libraries, which results in outstanding library quality.
The documentation of Elixir libraries is very good, with plenty of examples. Unlike some other languages, the standard library is also very well documented.
👍 Phoenix framework
The slogan of the Phoenix framework is “Phoenix just feels right”. Unlike frameworks in other languages, Phoenix has a lot of functionality built-in. Out-of-the-box, it supports WebSockets, routing, HTML templating language, internationalization, JSON encoders/decoders, seamless ORM integration(Ecto), sessions, SPA toolkit, and a lot more.
Phoenix framework is known for its great performance, being able to handle millions of simultaneous connections on a single machine.
👍 Fullstack Elixir
The Phoenix framework has recently introduced LiveView, which allows building rich realtime web interfaces right within Elixir (think Single-Page Applications). No JavaScript needed, no React!
LiveView even takes care of synchronizing the client and server state, which means that we don’t have to worry about developing and maintaining a REST/GraphQL API.
👍 Data processing
Elixir can be a solid alternative to Python for a lot of tasks related to data-processing. Having built a web scraper in both Python and Elixir, Elixir hands down is a much better language and ecosystem for the task.
Tools like Broadway allow building data ingestion/data processing pipelines in Elixir.
👌 Type System
In my opinion, the lack of proper static typing is the biggest drawback of Elixir. While Elixir isn’t statically typed, the compiler (along with dialyzer) will report a lot of errors at compile-time. This goes a long way over dynamically typed languages (like JavaScript, Python and Clojure).
👍 Speed
Elixir compiler is multi-threaded and offers blazing fast compilation speeds. Unlike Java Virtual Machine, the Erlang VM is fast to start. The runtime performance is very good for Elixir’s use cases.
👍👍 Reliability
Elixir builds on top of Erlang, which was used for over 30 years to build the most reliable software in the world. Some programs running on top of the Erlang VM have been able to achieve 99.9999999% reliability. No other platform in the world can boast the same level of reliability.
👍 👍 Concurrency
Most other programming languages have not been designed for concurrency. This means that writing code that makes use of multiple threads/processor cores is far from trivial. Other programming languages make use of threads that execute parallel code (and shared memory, that the threads read from/write to). Such approach typically is error-prone, prone to deadlocks, and causes exponential increases in complexity.
Elixir builds on top of Erlang, which is known for its great concurrency features, and takes an entirely different approach to concurrency, called the actor model. Within this model, nothing is shared between the processes(actors). Each process maintains its own internal state, and the only way to communicate between the various processes is by sending messages.
By the way, the actor model really is OOP as first intended by its creator, Alan Kay, where nothing is shared, and objects only communicate by passing messages.
Let’s draw a quick comparison between Elixir, and its imperative cousin Go. Unlike Go, Elixir was designed from the ground up for fault tolerance. Whenever a goroutine crashes, the entire Go program goes down. In Elixir, whenever a process dies, only that single process dies, without affecting the rest of the program. Even better, the failed process will get restarted automatically by its supervisor. This allows the failed process to retry the operation that has failed.
Elixir processes are also very lightweight, one can easily spin hundreds of thousands of processes on a single machine.
👍 👍 Scaling
Let’s draw another comparison with Go. Concurrency in both Go and Elixir makes use of message passing between concurrent processes. Go programs will run faster on the first machine since Go compiles to native code.
However, once you start scaling beyond the first machine, Go programs start loosing. Why? Because Elixir was designed from the ground up to run on multiple machines. The Erlang VM that Elixir runs on top of really shines when it comes to distribution and scaling. It seamlessly takes care of many tedious things like clustering, RPC functionality, and networking.
In a sense, the Erlang VM was doing microservices decades before microservices became a thing. Every process can be thought of as a microservice — just like microservices, processes are independent from one another. It is not uncommon for processes to run across multiple machines, with communication mechanism built into the language.
Microservices without the complexity of Kubernetes? Check. That’s what Elixir was really designed for.
👍 Error Handling
Elixir takes a very unique approach to error handling. While pure functional languages (Haskell/Elm) are designed to minimize the probability of errors, Elixir assumes that errors will inevitably happen.
Throwing exceptions is fine in Elixir, while catching exceptions generally is discouraged. Instead, the process supervisor will restart the failed process automatically to keep the program running.
👌 Learning effort
Elixir is a simple language, and one can pick up Elixir in about a month or two. What makes the learning somewhat harder is OTP.
OTP is the “killer feature” of Elixir. OTP is a set of tools and libraries from Erlang that Elixir builds upon. It is the “secret sauce” that significantly simplifies building concurrent and distributed programs.
While Elixir itself is pretty simple, wrapping one’s head around OTP can take some time, it certainly did for me.
👍 Learning resources
Being the most popular functional programming language, Elixir has a wealth of learning resources. There are a dozen of amazing Elixir books on the Pragmatic Programmers. The learning resources almost always are super friendly to beginners.
👍 Pattern matching
Elixir has great pattern matching support.
👎 Crunching numbers
Elixir doesn’t handle well computationally-intensive tasks. A compile-to-native language should be chosen instead for such tasks (Go/Rust are good options).
Image for post
Ok, what’s the deal with Erlang?
For all intents and purposes, Elixir and Erlang are identical under the hood. Erlang is a powerful language with a weird syntax. Elixir can be thought of as a nicer and more modern syntax for Erlang (along with a very nice ecosystem and community).
Image for post
Verdict
Image for post
Elixir probably is the most mature of all functional languages. It also runs on top of a Virtual Machine made for functional programming. It was designed for concurrency from the ground up, and is a perfect fit for the modern era of multi-core processors.
Watch the short Elixir Documentary to learn more.
Image for post
Awards
Image for post
Elixir receives two awards.
Its resiliency, functional-first approach, and amazing ecosystem makes it the Best Language for Building Web APIs.
OTP and the actor model make Elixir the Best Language for Building Concurrent and Distributed Software. Unlike its imperative cousin Go, software written in Elixir can scale horizontally to thousands of servers, and comes with fault tolerance out-of-the-box.
Why not use the right tool for the job?
Image for post
Photo by Haupes Co. on Unsplash
Would you use a screwdriver to drive a nail? Probably not. Then we probably shouldn’t attempt using one programming language for everything, every language has its place.
Go is the best language for system programming. The best option to use for frontend development is undoubtedly ReasonML, it ticks most of the requirements of a great programming language. The absolute winner for Web API Development is Elixir, its only drawback is a lack of static type system (which is being offset by a great ecosystem, community, reliability, and concurrency features). The best option for any sort of concurrent/distributed software is once again Elixir.
If you’re working with data science, then, unfortunately, the only reasonable choice is Python.
I really hope that this article was useful. Comparing programming languages is no easy task, but I did my best.
What are your thoughts and experience? Have I missed anything important? Should some language be ranked lower or higher? Let me know in the comments.. . . . . . . . . . . . . . . . .

--

--