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<String> element.classes.remove("my-class") element.classes.clear()
By the same token, you do not use
prepend to add an element to the DOM, because in Dart you merely add an element to a list of DOM nodes.
var button = new ButtonElement()..text="OK"; element.nodes.add(button); //element.nodes is List<Node>
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
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 of the 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 until 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 and reduce) on them. Using these we can express complex reactive computations in a declarative way.
var inputs = query("#query"). onKeyUp. map((event) => event.target.value). where((text) => text.length > 2). transform(new Throttle(500)). distinct(); inputs. map(queryWikipedia). where(validResponse). listen(printResults);
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. This has all been done with just a few lines of code.
Streams are a very powerful tool for expressing reactive computations, but that is not what I want to emphasize here. I want to emphasize 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 (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<KeyboardEvent> webSocket.onMessage //Stream<MessageEvent> mvcModel.onChange //Stream<ChangeEvent>
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's more, the standard library enables composability of other libraries. When working on your library you don't 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 you expect in your 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.
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.
Read More About Dart
Read more about Dart at engineering.nulogy.com.