The World Might be Missing a Programming Language

We have all the languages we need — perfection has been achieved. Or has it?

Alex Mills
Jan 4 · 13 min read
The proverbial silver bullet

Some criteria for a useful language:

  • It can be compiled/transpiled to run on multiple platforms, including UIs and servers. Easy transpilation to other languages is crucial to allow it to run in many places at the onset.
  • It makes writing asynchronous code easy — most runtimes have to handle an async event here or there, there is no avoiding it. The easiest way for the developer to avoid mistakes (and the need to put locks everywhere) is to design a language that doesn’t have (thread) pre-emption. For example, there is no pre-emption in Node.js — all your scheduled code runs first, then, when it finishes running, the event loop moves to the next tick. If Node.js had pre-emption, the platform wouldn’t have worked at all — the absence of pre-emption is one of the reasons why it’s such a powerful platform. I believe all JavaScript runtimes are bound to this constraint, but I am not 100% sure, (someone back me up?). On the other hand, Java has thread pre-emption and this makes asynchronous programming hard (especially to write libraries for general use). Pre-emptive software environments are simply bad, or morally wrong, if you will.*
  • It must compile to machine-code for performance. This of course does not prohibit the language from having a Virtual Machine as well, and being interpreted by that Virtual Machine instead of compiled to machine code (for the record, Java is compiled to bytecode then interpreted).
  • A VM/interpreter may be essential, because it is sometimes prohibitive to compile each and every program. Instead, for scripting we use interpreted languages because we can run programs without having to re-compile them for every machine.
  • It is structurally-typed*, not nominally-typed. Nominal-typing seems to offer few advantages to structural-typing, as far as I can tell. Structural-typing makes libraries less bloated and easier to write. FP languages are more often structurally-typed than OOP languages, and I think structural-typing is unequivocably better. Structural typing is especially useful for interoperability between libraries, otherwise both libraries need to import each other.
  • Has inferred* typing. Types should be inferred as much as possible, chaining and FP seem to help with this, whereas pure OOP seems to mean a lot of duplicate declarations.
  • It has no offside-rule — aka, no whitespace-as-syntax, aka no significant whitespace. Whitespace-as-syntax has to be one of the worst ideas in all language design, for obvious reasons. It is all about vanity and will end up killing you someday.
  • It is performant, with an easy-to-use concurrency model that can take advantage of modern multi-core processors. The event-loop model with async I/O seems to work really well, which is one big reason why JavaScript is so popular.
  • It has automatic garbage collection and memory management.
  • It avoids striving for hard-real time, instead going for soft-real-time. Hard-real time means pre-emptive or priority threading is used, which makes software development a lot trickier. Since 99.9% of software does not require hard-real-time features, this is something that a silver-bullet language can avoid, and therefore it can avoid pre-emption. Hard-real-time software might include control systems software, algorithmic trading software, etc.
  • It has the best of both worlds when it comes to functional/object-oriented, aka, why not both?
  • It has first-class functions. Template string literals. Anonymous functions, closures, etc.
  • It has solid language-level support for immutability and immutable data structures etc.
  • It has a good module system, packaging system, and dependency management system.
  • It allows for more than 1 version of a dependency loaded at runtime. Java does not allow this which makes for dependency hell — for a given class only one instance can exist, that’s how the classloader works.
  • The language itself makes writing 3rd party libraries easy.
  • It has a widely available and supported interpreter, since this makes WORA easier to implement.
  • It has easy deserialization/serialization. JSON is probably here to stay for awhile, but a lot of typed languages make it overly difficult to do I/O.
  • It is technically superior so that any barrier to entry is completely necessary, which has the helpful effect of keeping away unskilled, inexperienced, or uninitiated programmers.
  • I don’t think operator-overloading is much more than vanity (correct me if I am wrong), but I do think that method-overloading has advantages. Trying to find two or three or four different names for essentially the same routine can be annoying at best. Learning Golang has reminded me that not all languages have method-overloading — this makes me sad.
  • This is by no means a complete list and I will add more later.
We can see, using TypeScript, that `acceptsFooArg()` accepts anything with the right properties.
This is Golang, and will compile on 1.10.4
This is Golang, and will not compile :(
TypeScript can infer the return type of acceptsFooArg(), as it should.

How existing languages miss the mark

C++

I don’t know that much about C++ because I have never used it extensively, but I have read about it countless times. To my knowledge, C++ is very versatile and powerful, but lacks auto memory management which makes it dangerous to use. You can write desktop UIs, but writing UIs for Android, iOS, Web is not that easy, as far as I know. Clearly, the reason is that people choose to support other languages instead of C++ to run on their platforms.

Java

Java is an excellent language for a lot of modern software. The biggest problem I have with Java is that it has a nominal/nominative type system instead of a structural type system — this is why the Java ecosystem is so bloated. Over time, tools like GWT for writing web apps proved to be fairly unproductive compared to using modern JavaScript. But Java is great for servers and Android. To this day, it seems that Vert.x is the best system for writing Java servers. Also, Java programs are not especially easy to compile, which has contributed to the fact that excess tooling is necessary to get Java programs running. This is a headache and major reason why so many Java devs don’t know much about the command line and are dependent on the IDE tooling to get anything done.

Golang

Golang was promising but it is lacking in a couple of key areas. It is not very supportive of functional programming and lacks generics and inheritance which are useful OOP features. Golang does score major points with regard to being structurally-typed, proving that a structural type system can compile very quickly, however it requires redundant type declarations (see above) which other structurally-typed languages avoid (TypeScript and OCaml being some examples). Golang also has a somewhat restrictive circular dependency issue where two files in different packages cannot depend on each other (although perhaps all compile-to-machine-code languages have this restriction — OCaml seems to). So, Golang is good for writing simple high-performance servers — but what else?

OCaml

In my opinion, OCaml is the most promising of them all. However, the one thing it lacks is good concurrency. OCaml was created before the age of modern multi-core machines and never had a solid concurrency model, other than spawning another process. I believe it has threads but it also has a GIL. With ReasonML on the move, OCaml might be getting more mindshare. I see there are big efforts to improve OCaml concurrency including “multi-core” OCaml and binding the Libuv Event Loop engine used by Node.js to OCaml.

Erlang

Erlang is cool — a high-performance dynamic language, like JavaScript. JavaScript and Erlang are the only two dynamic languages that make this list. However, without TypeScript and Dialyzer, they would have made it. There are a couple problems with Erlang: 1. It is useful only to build scaleable backends — not UIs or mobile apps, etc. 2. It has type-annotations (which Dialyzer uses) but those type-annotations are nominative not structural. Using 3rd-party libraries and trying to figure out how they work without types can be a headache. Type-annotations might help, but I think they would be better if they were structural annotations. We could build mobile apps with Erlang, like we do with Swift and Java, but without a real type system this could get pretty tricky.

F#

F# is promising. It is a high-performance clone of OCaml and because it is Microsoft, it can borrow a lot of the nice libraries from Microsoft / C#. The problem is that F# is nominatively-typed because it borrows from existing MS libraries. Damn it! Still, nice developer tooling with VSCode. Unlike OCaml, F# uses optional significant whitespace.

C#

C# is nominatively typed, like Java and F#. It is perhaps better than Java, but still has nominative typing, making it bloated just like Java. Like F#, it has nice developer tooling though.

Dart

Dart is somewhat like TypeScript. One thing it lacks is method-overloading. It is not an especially functional language , at least no more so than JavaScript — it does not support immutability by default, for example.

Rust

Along with OCaml, Rust is highly promising. One thing Rust does appear to lack is method-overloading — a feature that I think is useful.

TypeScript > JavaScript

Besides OCaml, I believe that TypeScript and JavaScript are closer than most modern languages to reaching an ideal. TypeScript is an excellent solution to the fact that JavaScript is untyped. One major problem with JavaScript is that computation and floating point issues can make numerical libraries a bit unreliable. While JavaScript does have a good concurrency model, the concurrency model does have a couple of pitfalls that you need to mitigate — primarily errors thrown in asynchronously executed blocks. Ultimately, TypeScript can’t solve many of the fundamental problems that JavaScript has, although it is hands down the best type-system that I have ever worked with.


The Big Threat

The obvious threat is that with more and more languages, mindshare is spread too thin. We need some genius to come in and create the language to kill all others, but any more mediocre languages are going to cause more harm than good. One of the biggest reasons why languages and libraries are good to work with is the community that can help when you hit a tough problem. When people stop using a language en masse it is effectively dead — a classic network effect problem.

Identifying the counter-intuitive / lesser-known ideals

  1. I take it as self-evident that structurally typed languages are superior. Golang provides evidence that they can compile quickly and TypeScript shows us how wonderful structural typing is.
  2. It seems like transpilers can take the place of compilers in many cases, but in some cases people are wary of the added complexity. As far as I can tell, it’s better to have an interpreter like the JVM that can run everywhere, as opposed to transpiling many target languages. For example, with React Native I am wary of writing ReasonML that gets transpiled to JavaScript which then gets interpreted into native code via the bridge on Android and iOS.
  3. Why are there no compilers for interpreted languages and interpreters for compiled languages? It seems like this would allow for more Write Once read Anywhere (WORA). Obviously, this would take work, but also forethought.
  4. Incremental compilers like tsc-watch for TypeScript are fantastic, why don’t languages like Java have incremental compilers that behave the same way?

Room for improvement

  1. Process startup time. With things like Lambdas in the cloud, a process startup time for Node.js processes of 300ms or so might be prohibitive. To improve theprocess startup time of interpreted languages we could create compilers for them. Likewise, to get compiled languages to run everywhere, we could create interpreters/VMs for them.
  2. Using non-preemption at the language/runtime level. From what I can tell, most languages allow for pre-emption which can arbitrarily change the order of execution. The developer expects code to execute in a certain order but when doing asynchronous development pre-emption can change that. The developer can handle pre-emption manually with locking (although this can be painstaking and error-prone) but writing libraries for general use in multiple environments can be a lot harder when pre-emption is something that needs to be worried about. Garbage collection in Node.js.
  3. Incremental compilation. With Java for example, you have to recompile everything. JRebel will hot-load code but only after it’s been compiled, which remains your job. As mentioned above, I would like to see more incremental compilation tools for compiled languages. The only incremental compiler I have used is for TypeScript. That means that you have some dev server, which holds compiled code in memory and listens for changes to files.

Conclusion

Perhaps quantum computing will usher in this change. I would be willing to bet that only a big company will be able to get the adoption and the talent together to create a super-language.

Is there a language that I am missing? Which language do you think is the most powerful and versatile? Can you imagine a programming language that is better than any existing ones?

Better Programming

Advice for programmers.

Alex Mills

Written by

Programmer. Soccer, bicycle, and 90's hip hop enthusiast. Likes movies about music, stand-up comedy. Pronounces LaTeX latex and Technics technicks.

Better Programming

Advice for programmers.