A standard library is often seen as a convenience, but it is much more; it raises the level of abstraction and enables rich composition, which helps manage complexity when building large applications. In this article I will show how a standard library achieves that using Dart as an example.

If it looks like a List it will be a List

Let's start with something very simple. Let's look at the list collection type, which we all know and love. Since List comes with the Dart standard library, everything is built using it. When I say everything, I really mean everything. For instance, adding a class to an html element is done not via some obscure DOM API, but using standard list operations.

element.classes.add("my-class") //element.classes is List

By the same token, you do not use append or prepend to add an element to the DOM, because in Dart it is merely adding an element to a list of DOM nodes.

var button = new ButtonElement()..text="OK";
element.nodes.add(button); //element.nodes is List

These two examples show the Dart standard library playing the role of an anti-corruption layer. It hides all the weirdness of the DOM API and gives you a consistent view of the underlying platform.

Standard Library is an Anti-Corruption Layer

There are libraries in the JavaScript ecosystem doing the same thing (e.g., jQuery), but Dart goes far beyond that. It is not just a thin layer on top of the DOM. The Dart standard library provides building blocks that substantially raise the level of abstraction. One of them is Stream.

Reactive Programming with Streams

Streams have been added to the Dart platform to help deal with the asynchronous nature of Dart programs. They provide a unified interface to anything that can send out a series of events. An example of a stream would be all keyup events on some input element. When a user presses a key, an event is pushed to the stream, and everyone listening will be notified.

Now, it gets interesting. If you look at these two pictures of a stream and a list, you will notice that they look exactly the same.

Think about it, lists and streams are both just sequences of values. The only difference is that lists are pull-based sequences and streams are pushed-based sequences. In other words, when working with a list, you are pulling values from it, till you reach the end of it. A stream, on the other hand, pushes values to you, until there is nothing left.

Since streams are very much like regular collections, the standard library defines familiar collection operations (e.g., map, filter, reduce) on them. Using these we can express complex reactive computations in a declarative way.

var inputs =
map((event) => event.target.value).
where((text) => text.length > 2).
transform(new Throttle(500)).

Here I am taking a stream of all keyups and mapping it into a stream of values. Then, I am doing some filtering. After that, I am ignoring fast typing to make requests only when the user pauses for more than half a second. Next, I am querying wikipedia. Finally, I am filtering out invalid responses to print only valid results. It has all been done with just a few lines of code.

Streams are a very powerful tool for expressing reactive computations, but it is not what I want to emphasize here. It is the importance of the standard library. Since the standard library provides streams, everything that is essentially a sequence of some sort is represented as a stream. It is not just keyboard and mouse events. A web socket object has a stream of messages. A backbone-like library gives you a stream of changes for a particular model.

inputElement.onKeyUp //Stream
webSocket.onMessage //Stream
mvcModel.onChange //Stream

They all have the same interface, which allows us to use the same decorators and combinators. So the throttle transformation can be written once and used everywhere.

Standard Library => Object Composability

What is more, the standard library enables composability of other libraries. When working on your library, you do not choose what async primitives to support. At the very least you will support the standard ones. Which means that the stream you return from your library will be the one I expect in my library. It makes libraries more composable.

Standard Library => Library Composability

Raising the Level of Abstraction

The fact that something like Stream is provided by the standard library makes a big difference, because it can be used at the API boundary of other libraries. Thus, most Dart libraries work with streams.

In JavaScript, on the other hand, even if you build a library on top of Bacon.JS, you cannot make Bacon.JS a part of your library's API. Because if you return a Bacon.JS stream, the majority of other libraries will not know what to do with it, which forces you to use callbacks. That is why everyone saying that JavaScript is flexible enough to implement anything is missing the point. Sure, you can implement streams in JavaScript, but to really raise the level of abstraction you have to make them ubiquitous.

Wrapping Up

The standard library acts as an anti-corruption layer hiding the inconsistencies of the underlying platform. It also makes abstractions ubiquitous, which raises the level of abstraction.

The absence of a standard library, in my view, is the weakest part of JavaScript, which is much harder to ignore than, for instance, some obscure language features. Dart, on the other hand, comes with one, and this fact alone makes Dart a viable alternative.