First look at Dart

In this post I want to give you a short introduction to Google’s new language called Dart.

Personally I am quite disappointed that Dart looks a lot like Java 8 with some tweaks here and there. Although the language is in its early stages I wonder why pattern matching was not a top-priority to the language team since Dart relies heavily on message passing. More on that in a second.

Since Dart should scale from small scripts to full applications it is nice to see support for generics from the start, optional typing and functions as first-class citizens.
However that makes Dart look more like a better Java than JavaScript.

What makes Dart more of a web language is how you perform concurrent computations. This is done by shared-nothing message passing. The same way you do it with workers in JavaScript, actors in Erlang or Scala and hopefully quite soon in the Flash Player. We should not forget Go in this equation since it was also an effort to try out concurrency via channels that can send and receive messages. If we look a little bit more into Dart we can see some of the same ideas.

A worker in Dart are called Isolate [1]. This makes sense since it runs completely isolate from the rest of your program. What is nice about the Dart approach is that you do not have to deal with files anymore when using an Isolate. JavaScript requires you to perform the task of having some special file lying around somewhere that adheres to the Worker protocol. When I write my application, I do not want to think about files that have some special top-level logic in them. Especially, I do not want to write a custom file at runtime using a blob-builder.

Dart embeds this from the start just like Go does. You will have a lot of Isolate objects communicating via a SendPort [2] and a ReceivePort [3]. The Promise<T> [4] is just like Java’s Future<T> a holder for a value that can be computed later. I just wonder why they did not implement a read-only and write-only view on them. It would be nice if Promise<T> would be extended with methods like map, flatMap etc. because your code would be less spaghetti.

With no further ado I would like to explain some code now that you know how Dart works. Basically we calculate the n-th Fibonacci number with a very expensive approach. You would never do this in reality. Let us define fib(x) = fib(x - 1) + fib(x - 2) with fib(1) = 1 and fib(2) = 1. So fib(3) is fib(2) + fib(1) which is 1 + 1. Sorry for to bore you to death.

If we implement this in pseudo-code we get something like this:

int fib(int x) {
  return x < 3 ? 1 : fib(x - 1) + fib(x - 2);
}

If you would like to make this multi-threaded with Scala you could wrap the calls to fib into a Future[Int] like this:

def fib(x: Int) =
  x match {
    case 1 | 2 => 1
    case _ =>
      val n1 = future { fib(x - 1) }
      val n2 = future { fib(x - 2) }
      n1() + n2()
  }

We want to do the same with Dart. Since it is a language targeted for the web we get no access to blocking calls. The Scala code blocks when n1() or n2() is called. In layman's terms: we wait until the value has been computed.

Since there is no support for continuation passing style like C#'s async we have to write a lot of callbacks now.

This is the entry-point for the Dart version of this:

main() {
  int n = 7;

  print("Computing fib($n) ... ");

  new FibIsolate().spawn().then(
    (port) =>
      port.call(n).receive(
        (value, port) => print("fib($n) = $value")
      )
  );
}

First of all we see string-interpolation which is nice. Then we create a FibIsolate which I will show you in a second. When you create an Isolate it does not do anything so we have to call spawn() to perform the actual computation. However spawn returns us only a Promise<SendPort> which we can only use when available. This is done with the then method. then takes a function as an argument.
There are multiple ways we could do this. then((x) => ...) is the same as then((x) { ... }). You are only allowed to use the first form for a single expression but you do not need to write all that boilerplate. In fact you are even allowed to omit a semicolon. Hell Yeah!

So when we get a SendPort which happens to be the case when our function is called we can send some value via the call method. Why use call and not send? call returns a ReceivePort which allows us to wait for the result. We do this by calling receive on the ReceivePort with a closure to print the value we get back.

Now let's have a look at the implementation of FibIsolate.

class FibIsolate extends Isolate {
  main() {
    port.receive((n, replyTo) {
      switch(n) {
        case 0:
          replyTo.send(0); break;

        case 1:
        case 2:
          replyTo.send(1); break;

        default:
          Promise<int> n1 = new Promise<int>();
          Promise<int> n2 = new Promise<int>();

          new FibIsolate().spawn().then(
            (port) =>
              port.call(n - 1).receive(
                (n, port) => n1.complete(n)
              )
          );

          new FibIsolate().spawn().then(
            (port) =>
              port.call(n - 2).receive(
                (n, port) => n2.complete(n)
              )
          );

          n1.then(
            (x) => n2.then(
              (y) => x + y
            )
          ).flatten().then(
            (x) => replyTo.send(x)
          );
      }
    });
  }
}

First of all we have to extend from Isolate. The main() function of an Isolate is its entry-point. Now something that really bugs me is the amount of indentation. Nearly every second Isolate which you are going to write has this form. Your actual logic starts at the fifth indentation level.

In the main method we immediately start listening for messages via the Isolate's ReceivePort. Then we need to react on the message via a switch case. Now although it might look simple in this case it is very sad that Dart does not come with proper pattern matching since you will need to write a lot of switch-case in Dart code. If you have ever seen pattern matching in action using Scala you never want to go back to a dumb switch statement.

In the default case it gets a little bit more interesting. We need to spawn two new instances of FibIsolate since we want to do this recursive. We need to receive their result and when we have both results we want to perform the addition and send our value back to the caller.

Since there are no blocking calls we create a Promise<int> for each FibIsolate. When can complete the Promise<int> once we receive a value from an isolate. This allows us to combine both Promise<int> objects and wait on their results. Why do we not nest the FibIsolate you might ask. Because we want to spawn both at the same time so the computation can happen in parallel.

The actual nesting happens in the n1.then((x) => n2.then((y) => x + y)) construct. However the return type of n1.then((x) => n2.then((y) => ...) is no longer Promise<int> but Promise<Promise<int>>. Fortunately someone thought about this case and there is a flatten() method which turns the Promise<Promise<int>> into a Promise<int> we just have to await and the finally reply with the computed value.

However remember the definition of fib(x)? The case for 1, 2 and 0 is quite simple because we can reply with the value immediately. And we do this by calling send on the SendPort which I named replyTo.

You can take a look at the full example running in the browser here.

What I like about Dart is that someone thought about workers and made them first-class citizens. Pattern-matching is on the road map and they will hopefully copy Scala. There are however a lot of things that I dislike.

  1. Semicolons are required but optional for shorthand (x) => x
  2. The return keyword is required but optional when used with (x) => x
  3. No control about concurrency. Are my isolates CPU or I/O bound?!
  4. No syntactic sugar for writing an Isolate
  5. Not DSL friendly like Scala or maybe even Kotlin
  6. int disguises itself as a primitive but is an object instead
  7. Missed the opportunity to embed continuation passing style into the language from the start
  8. Function is the only function-type

The type-system can be argued as well. I did not find anything about type erasure yet. Personally Dart feels much more like Java and Go than JavaScript. But in the end I would be quite happy if Dart could replace JavaScript since it is a step in the right direction from my personal point of view.

[1] http://www.dartlang.org/docs/api/Isolate.html#Isolate::Isolate
[2] http://www.dartlang.org/docs/api/SendPort.html#SendPort::SendPort
[3] http://www.dartlang.org/docs/api/ReceivePort.html#ReceivePort::ReceivePort
[4] http://www.dartlang.org/docs/api/Promise.html#Promise::Promise

One Trackback

  1. By #FollowFridayLinks S01E02 | AS_Blog on Oct 29, 2011 at 2:09 pm

    [...] [JS] First look at Dart ยป [...]