Dart versus TypeScript
In recent posts I have shown that the web only has crummy technologies, but at the same time, Flutter deployed on the web is not yet free of its own crumminess to compensate.
In this post I shall convince you, beyond any doubt, that to develop frontends, you should use the Dart language rather than TypeScript. We'll examine:
- Problems with JavaScript
- Ignoring those problems like ostriches
- Problems with TypeScript
- Problems with functional languages
- Dart as a solution
- Problems with Dart
- Conclusion
- Futurology
1. Problems with JavaScript
JavaScript was created in 10 days and now we have to tolerate it forever!? WAT. It is the only language I know with so many evil parts, other than INTERCAL.
The best book about it is called "JavaScript – the good parts". Everyone has read that book. However, its author now says it's time to stop using the language.
The design problems in the JavaScript language are too numerous to list here, but here are some of the most egregious:
-
this
keyword-
Context sensitivity: The value of
this
can change depending on the context in which a function is called, leading to unexpected behavior and countless debugging sessions for thousands of developers. -
Binding issues: Developers often need to use .bind(), call(), or apply() to explicitly set
this
, which is cumbersome and unheard of in any other major programming language. -
Arrow functions: Arrow functions do not have their own
this
context, which can be both a benefit and a source of confusion when switching between arrow functions and regular functions.
-
Context sensitivity: The value of
-
Arrow functions
- Implicit return: The concise syntax can be misleading, especially with object literals, where {} is interpreted as a block rather than an object.
- Arrow functions do not have their own
arguments
object, which can be limiting in certain scenarios. -
No own
this
: While it solves some problems, it can also be confusing when developers expect a traditional function'sthis
behavior.
-
Type coercion
-
Implicit coercion: JavaScript's automatic type conversion is a severe misfeature that leads to unexpected results, such as
'' + 1
resulting in'1'
ortrue + false
resulting in1
. -
== vs. ===
: The loose equality operator == performs type coercion, which can lead to unexpected results, whereas === does not, leading to a general preference for the strict equality operator but also to confusion among new developers.
-
Implicit coercion: JavaScript's automatic type conversion is a severe misfeature that leads to unexpected results, such as
-
Classes and super()
-
Syntactic sugar: JavaScript classes are often criticized for being syntactic sugar over the prototype-based inheritance, which can lead to misconceptions about how inheritance works in JavaScript. In reality this is not a problem in itself, except for all the terrible implementation details in classes,
this
andsuper
, which become a list of gotchas for developers to memorize. -
Mandatory call: In a derived class, if you define a constructor, you must call super() before you can use
this
. Forgetting to do so results in a reference error. -
Order of initialization: The call to super() must happen before accessing
this
, which can complicate constructor logic and initialization sequences, or even make one's idea impossible without a redesign. - Classes in JS are so bad that most JS developers prefer to ignore them entirely. Instead, they achieve encapsulation by abusing closures, which is in itself another terrible way to write software.
-
Syntactic sugar: JavaScript classes are often criticized for being syntactic sugar over the prototype-based inheritance, which can lead to misconceptions about how inheritance works in JavaScript. In reality this is not a problem in itself, except for all the terrible implementation details in classes,
-
Module systems
- Due to historic reasons, JavaScript has multiple module systems (CommonJS, AMD, ES6 modules), which can be confusing and lead to compatibility issues.
- The final system (ES6 modules) has a
export default
feature which I don't see in any other language, does not add value per se, and probably only exists to emulate the previous 2 module systems.
JavaScript is a language notorious for its inconsistencies and flaws. Despite possessing modern features, it suffers from fundamental issues that remain unresolved. this
, function
, arrow functions, super
, and the, so to speak, excessively dynamic type system... the behavior of these things is riddled with exceptions and unexpected outcomes. Learning JavaScript often feels like memorizing a long list of workarounds.
As a result, JavaScript is a language that makes kittens cry every day. It is legitimately a language to be hated, if we are being reasonable.
JavaScript is a hypocrite, like a person who pays for expensive, albino-white facades on their front teeth, but leave their back teeth to rot full of caries. JavaScript is the guy with a sports car who in truth is hurtful to women.
People who decide to use JavaScript outside of the browser are backwards: the browser should acquire a good language, instead of the worst language contaminating the entirety of computing.
The real reason every other language compiles to JS, and the real reason WASM exists, is not a lack of cool new features in JS. The real reason is that in JS, this is broken, function is broken, arrow functions are broken, super() is broken, the type system is broken... To learn JS is to learn a pointless list of exceptions to expected behavior.
Consequently, many developers choose to use languages that compile to JavaScript or explore alternatives like WebAssembly. This trend highlights a critical issue: JavaScript's fundamental flaws hinder development efficiency and cost lots of time and money.
As an example, here is a lesson for today:
- Arrow functions cannot be constructors.
- Arrow functions do not inherit a
this
binding; if they are part of an object, they cannot talk to it. - Arrow functions don't provide
arguments
; normal functions do. - Named functions are hoisted,
const
is not. Arrow functions were invented to be anonymous and to make small event handlers and callbacks, but people are abusing them and naming them withconst
.
This is just one confusing instance where JS has 2 ways of doing the same thing, both with advantages and disadvantages depending on what you are doing. How much of this will you remember in 30 days?
Why not pick a good language instead?
In short, what needs to change in JavaScript is its WAT. And while that doesn't happen, hordes of young programmers are learning a horrible programming language first. Getting used to the most inelegant solutions. Honestly, JavaScript has become the most popular programming language, and also the worst popular programming language. The only things that are worse are those that are designed to be worse: esoteric programming languages like INTERCAL and Whitespace.
But the worst part is, they seem to have given up fixing JavaScript. They have concluded it's impossible, due to the requirement of eternal backwards compatibility. That is the wrong conclusion, and it shall be revised real soon now, as web development has clearly become unsustainable.
2. Ignoring those problems like ostriches
Most JavaScript developers are the proverbial boiled frog. They have been studying this cursed language for years and years, why worry now? "I am productive in JavaScript in spite of its shortcomings." Their attitude is that of the ostrich: "learn the good parts", shun the bad parts, and develop code today.
They will add, that all the alternatives to JavaScript are also doomed, for other reasons. Maybe they are harder to debug in the browser. Their performance is necessarily worse than JavaScript, since they compile to JavaScript. And so on.
In short, it's the famous Sunk Cost Fallacy. JavaScript is evidently not beneficial, but one sticks with it due to past investments.
Where Python 3 focused on removing all the warts from Python 2 and succeeded, people imagine this to be impossible in JS, since they believe there is an eternal backwards compatibility requirement. I predict this requirement will drop very soon, as the accumulation of horrible web standards becomes a terrible burden.
Yet, a successful precedent exists: ActionScript 3 introduced class-based inheritance, separate from, and without disrupting, the existing prototype-based system. This demonstrates that it's feasible to evolve a language without breaking existing code.
Again: It is NOT impossible to fix JavaScript; the impossibility is an illusion that makes you accept JS.
The only thing that is even more painful than fixing a floor full of rusty nails pointing up... is to forever tolerate it. But that's exactly what a boiled frog does. "I already know where the rusty nails are, I don't step on them anymore."
This almost amounts to a Human Rights issue.
My advice to you is: are you writing a large web app? Then for the love of humanity, do it in anything but JavaScript.
3. Problems with TypeScript
There is a moderately popular project by Facebook called Flow. It lets you write static type annotations on otherwise JavaScript code, it checks the types as you write, and then it simply removes the type annotations in the end, leaving only your JS code. I consider Flow a good design – if you need to write JS, that is.
Microsoft answered the same question differently.
They hired Anders Hejlsberg, the guy who had created Turbo Pascal and Borland Delphi, to make derived languages for them. First they used him in their attempt to Embrace, Extend and Extinguish Java. Microsoft then lost a tremendous lawsuit to Sun Microsystems for that misstep, so they turned to the next best war strategy: make their own Java while denying all influence. Thus C# and the Dot Net Framework were born, or rather, cloned. To this day these people are affirming that "C# belongs to the C family of languages", while it really is Java with a couple of misfeatures removed. Hejlsberg was and is the main designer of C#.
In 2012 Microsoft announced another Hejlsberg creation: TypeScript, which has become the most popular compiles-to-js language. But instead of just adding types to JS (like Flow does), it is a bastard child of JS and C#. I imagine they gave Hejlsberg these contradictory goals: "We want C# for the web, but it also must be a superset of JavaScript". The superset bit means, if you paste JS into a TS file, it just works – all JS is valid TS. It also means TS has its own separate features, augmenting JS.
The fact is, this one-way compatibility with JS is probably why TS won. But you know what I am going to say, right?
TypeScript again decides not to fix any of the bad parts of JavaScript. TypeScript is a monstrous creation, it adds even more cool features, such as algebraic types, without first fixing the basics. The decision to be a superset of JS sealed TypeScript's fate; after that decision, being a good language was impossible. It presents the best language features and the worst language features in a single thing. TypeScript is the most hypocritic programming language in the world, and as such, it could only have been born at Microsoft. Or Oracle, Apple, Facebook or Google.
Learning TypeScript is learning tens of weird unexpected syntaxes in the type system – things that should be natural and much easier – and then forgetting them while you are coding.
Every developer has noticed that, if TS seems powerful, it is because there's an enormous amount of features for annotating types. It's not simple at all, it amounts to a tremendous cognitive burden. And newer versions never simplify anything, they only add to that burden. The developers of TypeScript take too much freedom to make it impossibly complex. I have found this frustrating, and I am not alone:
Rich Harris: "We also eliminate an entire class of annoying papercuts that will be familiar to anyone who has worked with the uneven landscape of TypeScript tooling."
Here is a video detailing the latest TS release. And here are some YouTube comments sharing my sentiment:
@tacochub4353: These updates are neat... sure, but I don't really see how these methods solve the plethora of issues with using TypeScript. All they seem to do is add unnecessary complexity to an already perplexing ecosystem filled with syntactical nuances.
@JamesDSchw: My beef with many TS releases over the years surround the cognitive load they incur - more syntax and language semantics to be able to model types in existing libraries in the ecosystem.
@universe_decoded797: Typescript solving things that are not problems to create more problems is problematic. ‘Simple things are hard to create’ is a true statement.
In short, you wanted to fix JavaScript and suddenly you saw TypeScript. It overloaded your senses with so much information and impression of power, that it seemed to be the right solution. The only thing everyone forgot was the actual problem: we need to fix JS.
To choose TypeScript, one must overlook two facts:
- There is tremendous value in keeping language scope down to a minimum. Until Python 3.4 more or less, Python was a small language, any programmer could pick it up in a week by reading a 100-page description of the language. And then they could learn crucial parts of the standard library in a couple of months. One would become productive very quickly. Unfortunately, Python has entered a new phase, in which they forget the value of staying small and keep adding syntax. Becoming Scala, a language that one never finishes learning. If you are a Scala programmer and you start reading another developer's code, chances are, you have to stop and look up this syntax that is new to you. That is a horrible mistake. Back to TypeScript, it starts by accepting JavaScript, but then paradoxically it again becomes Scala by relentlessly adding features.
- We need a language that is a solid base to build upon; the perplexing crumminess of JavaScript is automatically unacceptable if mental health is a value.
4. Problems with functional languages
It is currently my opinion that an object-oriented programming language is perfect for creating user interfaces, even a pure OO language such as Smalltalk.
But here, let us ponder that an object-oriented approach greatly benefits from adopting a few lessons from functional languages. Functional programming is not the opposite of object-oriented programming; to a certain extent these can be combined. Also, object orientation today accepts that composition is better than inheritance most of the time. I favor a pragmatic approach that uses notions from both these worlds. Immutability only on certain kinds of information, and a conscious effort to create pure functions and unit tests for these – these are key to writing good code.
But the current wave of functional programming languages is another thing that a healthy reader should doubt. In about 15 years of people trying functional languages and immutability in the browser (either in JS or in functional languages such as Elm, Elixir and ReScript), the functional paradigm and the insistence on immutability have failed to deliver the cleanliness and developer productivity that were promised.
Here are some arguments so we can establish that functional languages and techniques are not the panacea:
-
Complexity in state management
- State overhead: Functional programming emphasizes immutability, leading to frequent state copies. This can increase memory usage and overhead.
- Verbose code: Functional paradigms often require more boilerplate code to manage state changes in an immutable manner compared to traditional imperative approaches.
- Business Logic Complexity: For complex business logic, imperative programming often provides more straightforward solutions, whereas functional programming can lead to overly abstract and convoluted code.
-
Steep learning curve
- Conceptual barrier: Functional programming concepts like higher-order functions, monads, and pure functions can be difficult for developers to grasp, purity being the easiest. Mathematical concepts are of course beautiful in computing, but they simply are not the way most people communicate – and the web should be for everyone.
- Limited adoption: The steep learning curve has hindered widespread adoption, making it harder to find developers skilled in functional programming, which impacts team productivity.
-
Performance concerns
- Inefficiency in browsers: Functional programming can introduce performance issues in the browser, such as excessive garbage collection due to frequent object creation from immutable state changes.
- Lack of optimization: JavaScript engines are primarily optimized for imperative code, potentially leading to less efficient execution of functional code.
While above I have tried my best to talk ill of functional languages… knowing what I know today, to develop user interfaces, I would reach:
- first for a multi-paradigm expressive language such as Python, Dart or Kotlin;
- then for a pure OO language such as Smalltalk;
- then for a hybrid functional language such as ReScript, OCaml or F#;
- then for an opinionated, pure functional language such as Elm or Haskell;
- then for anything else in existence;
- before resigning myself to use TypeScript or JavaScript with their broken basics.
5. Dart as a solution
In 2009, Node.js brought JavaScript to the server, and now boiled frogs write their backend and frontend in the same language: the worst one. Someone help them!
Seeing this, Google unveiled their Dart language in 2011. You can think of it as the last Java clone, this time with better influences. Dart 1.0 came out on November 2013.
The initial plan for Dart was to include it in Chrome as a second native browser language, the good brother of JavaScript. This was criticized for fragmenting the web, so they gave up this idea in 2015 at the release of Dart 1.9. And then Google proceeded to dominate the web anyway – through countless bad standards – such that now it is financially impossible for anyone else to develop a new browser. We might as well have had Dart in Chrome, it would have been a tremendous blessing all these years.
There exists a parallel universe in which the frontend community gladly accepted Dart as their saviour when Google proposed it as a sane, parallel native language in the browser. I wish I lived in that universe. Frontend devs, you have Stockholm Syndrome.
Instead, Dart was sort of forgotten for a couple of years while Flutter was being developed. It was released in 2018.
Here are reasons why Dart is good for developing applications and GUIs:
- It has none of JavaScript's defects.
- It is essentially just another boring multi-purpose Java clone with a few saving graces.
- It has null safety.
- It has a good, pragmatic type system without any trace of TypeScript complications. Something that just helps programmers instead of stealing their attention.
- It has garbage collection.
- Very performant for a garbage-collected language. You can roughly think of Dart as 10 times faster than Python and 10 times slower than C++.
- It runs on every platform; it can also compile to JS.
- It is now beginning to compile to WebAssembly and even use its garbage collector – this makes the runtime smaller.
- It is being developed at a nice pace.
- It has a moderate, good enough syntax size; it does not seem to want to become Scala.
- It has features to write constructors without so much boilerplate, making Java look silly. However, Python is still better in this regard.
- In fact, in some places it has been smarter than Java and C#. For instance, it does not have the
private
,protected
andpublic
keywords; instead, the programmer simply starts a variable name with an underscore (such as_myVariable
) and that makes the variable private to the current file. This is great language design, removing lots of noise in a single movement. - Developer productivity and comfort are higher in a no-nonsense, immediately familiar language.
- If you learn Dart, you are learning the language of Flutter. If you write the core of your web app in Dart, you can later reuse some of that core in a mobile app. And Flutter is much better than React Native... because React Native is based on JS/TS, and its misarchitecture consists of letting you use crummy web technologies such as CSS (or rather, just an arbitrary subset of these technologies) which then get translated into native widgets. It is just a fundamental lie, designed to keep web developers in their narrow comfort zone.
6. Problems with Dart
- Metaprogramming/reflection is currently weak in Dart, but being worked on right now (2024), with macros in the roadmap for the next few releases.
- Interop with JavaScript is more difficult than expected. Using a JS library in Dart code is fine, but you have to write typing stubs for the library's interface. Consuming Dart code from JS requires you to expose objects and functions with a decorator, and I don't think you currently can expose them in a JS module, so you have to put the API on the window object, which feels outdated.
- Indentation with only 2 spaces is hard to see.
- It uses curly braces instead of significant indentation. (Significant whitespace is objectively better because it communicates the same information with less visual noise and occupies fewer lines.)
- It requires semicolons.
- In Python, None, 0, "", [] and {} are all falsy, because they do not contain information. That's very useful. But in Dart, only null is falsy; the others are truthy. This makes comparisons more verbose to write in Dart. (And in JavaScript comparisons are a minefield.)
Given the above, I would definitely write web apps in Dart, especially using its numerous frameworks for doing so; I would also write a large app component to be consumed by JS through a relatively small interface; but I would not write a typical JS library in Dart, unfortunately.
7. Conclusion
Going parallel to JS is unavoidable, that is why everyone wants Web Assembly to succeed: it's the only escape.
Dart is not perfect, but programming in it is bliss compared to JavaScript and TypeScript. There are alternatives out there; your responsibility is to choose something better than what everyone else is using, if you are smart.
8. Futurology
The parallel solution is soon going to be required for the entire Web, not just JavaScript. Because the powers that be have introduced an enormous number of spectacularly failing standards:
- JavaScript
- HTML that is not XML
- CSS, which again is growing impossibly as a language, is impossibly complex in the interaction of its features, contains an impossible number of footguns, and is already humanly impossible to learn for its target audience of designers and common people
- Web Components (Custom Elements), which are enormously complex, have a terribly verbose API, yet somehow manage to fail at addressing basic concerns of writing GUIs
- IndexedDB, the only way for frontend devs to access a SQLite database, has a horrendous API, so nobody uses it
- ...?
The idea that these bad standards, plagued by complexity and inconsistencies, must remain in the Web forever for backwards compat is absurd and impossible. Of course one day this entire mess will be dropped.
The web platform has become so convoluted that only tech giants can afford to build browsers. This centralization of power threatens the open nature of the web.
When Firefox finally finishes failing, we'll be in the impossible situation of every browser being based on Chromium. This is due to the number of incredibly complex features and standards that a browser must implement. Thus the web no longer belongs to the people, it belongs to tech giants.
I am calling this right now: soon the people will create a "New Simple Web", from scratch, with simpler (but not necessarily more powerful) technologies, languages and protocols, to replace this Impossibly Big Ball of Backwards Compatible Spaghetti. This revolution will be painful in many ways, but it is clearly unavoidable. The most important values for the right technologies, languages and protocols will not be power, but cleanliness, simplicity and developer experience.
I believe the New Simple Web will look more like Flutter than anything else. It will be based on a single good language. No separate language for formatting. It will tend to the pragmatic needs of writing applications. But it will still somehow make contents public, as they are today. Oh, and it will have no DRM.
In order to become popular, the New Simple Web will have to offer something to the users, too. Evidently, that something will be their freedom. By then Google will already be the distopian oppressive OCP they have decided to become, so they will be closing everything on the Web: mandatory ads, mandatory privacy invasion, mandatory taxes, mandatory DRM protecting THEIR content which they actually stole from books, poorly paid videomakers etc... you name it. This is what Chrome will be.
Other tech giants will try to create an Alternative Web in advance, but they will not provide the necessary freedom, and therefore they will fail.
Someone will rise to the challenge, present a clear picture of how the New Simple Web should be built, and do it. People will use Chrome for banking and gradually migrate to the New Simple Web for everything else.
And then the cycle will begin again, inasmuch as humans are bound to forget learned lessons.