Wednesday, 05 February 2014

Dart vs. ClojureScript: two weeks later

A couple of weeks ago, I wrote about my first impressions of the Dart programming language in preparation for GDG Houston’s Dart Flight School event coming up on the 22nd of February. Since then, I have finished the new code lab, the Dart tutorials, and the AngularDart tutorial. For comparison’s sake, I did all but the AngularDart tutorial in both Dart and ClojureScript to get a feel for the differences between the two languages. I have published my ClojureScript implementation of the Dart tutorials on Github.

After working my way through all of this code, what’s my take on Dart and ClojureScript? I’ll start with addressing errors from my previous post and then compare Dart and ClojureScript in the following areas:

  • Standard libraries
  • Ecosystem
  • Tooling
  • Debugging
  • Documentation
  • Outside the browser
  • Integrating with Polymer
  • Integrating with Angular
  • Asynchronous programming support

Errata from ‘First impressions’

Before I get to the comparisons, I would like to correct some things I got wrong last time.

Static typing

Dart supports a mix of dynamic and static typing. You can program in Dart without ever declaring a type. However, without the static types, static analysis tools available for Dart will be less effective. Nonetheless, it is a choice you get to make. For example, take the following program:

void main() {
  var noType = “foo”;
  noType = 2;

  String withType = “foo”;
  withType = 2;

  print(“$noType $withType”);
}

An IDE or the dartanalyzer program will flag line 6 above and give you a warning that A value of type 'int' cannot be assigned to a variable of type 'String'. Nonetheless, the program will run just fine and output 2 2. However, running the program with ‘checked mode’ enabled (either as a runtime option to the Dart VM or a compile-time option when compiling to JavaScript) will produce an exception at line 6 with a message akin to type 'int' is not a subtype of type 'String' of 'withType'.

There is one place where Dart’s type system does irk me: only false is false and only true is true. In the following program, all of the print statements will print that the value is false (in unchecked mode):

bool isTrue(var v) {
  if (v) {
    return true;
  } else {
    return false;
  }
}

void main() {
  print(“0 is ${isTrue(0)}“);
  print(“1 is ${isTrue(1)}“);
  print(“empty string is ${isTrue(")}“);
  print(“non-empty string is ${isTrue(“foo”)}“);
  print(“null is ${isTrue(null)}“);
}

In this case, the static analyser does not find any problems with the program, but running it in checked mode will produce a type error since the expression for the if condition must be a boolean type.

I have grown used to things like null pointers or zero being false and non-null pointer and non-zero integers being true. I find needing to explicitly make an equality check annoying.

Clojure has core.typed, a library to add gradual typing to Clojure programs. However, using it is not nearly as seamless as is choosing to use static typing in Dart.

Serialisation

This is one area where I got a lot of feedback last time. First, a few points:

  • It is idiomatic Dart to serialise to JSON.
  • Dart’s JSON library can automatically handle num, String, bool, and Null types. List and Map objects can also be automatically serialised subject to a few constraints.
  • Dart also has a serialistion library that serialises objects reflectively or non-reflectively. It is fairly powerful and highly customisable.

What’s the fallout from the above with respect to ClojureScript? I have a couple of thoughts:

  1. ClojureScripts’s extensible data notation (EDN) is richer than JSON, making it deal with complex data structures and retain semantic information. For example, is a list of items intended to have constant random access (a vector) or contain unique elements (a set)? Additionally, it is extensible and allows you to add support for application-specific types.
  2. ClojureScript’s data-centric approach (using the built-in data structures rather creating new types) makes serialisation very easy. If you follow the same approach in Dart, you can enjoy many of the same benefits. However, as soon as you introduce new types in either language, the situation becomes more difficult.

In conclusion, it seems like if you stick with built-in types, both languages have a comparable serialisation story. Nonetheless, I think that the idiomatic Clojure approach to data combined with the richness of EDN gives it an edge over Dart.

Dart vs. ClojureScript

Now that I have spent some more time with both languages, I can make more informed and helpful comparisons between the two. One thing to keep in mind is that these experiences come from going through the Dart tutorials, so they may not play to ClojureScript’s strengths.

Standard library

Dart takes a ‘batteries-included’ approach to its standard library, making it easy to write both web and command-line applications without depending on external libraries.

In comparison, ClojureScript is very much a hosted language. While it has superior support for functional programming and manipulating data structures, when the time comes to move on from solving koans to writing a real application, you discover you have to learn your target platform and how to interoperate with it.

I think of it this way:

Developer: Hi, I want to write a web application.

ClojureScript: Great!

Developer: Um… how do I get a reference to a DOM element?

ClojureScript: That depends.

Developer: On what?

ClojureScript: Well, are you going to use JavaScript directly, use Google’s Closure library, or try a ClojureScript library like Domina or Enfocus? There are also radical alternatives to manipulating the DOM like Pedestal and Om.

Developer: Uh… I don’t know.

Developer spends the next half-day evaluating the ClojureScript options.

Some days later:

Developer: Well, now I need to do something different. I need to use HTML5 IndexedDB.

ClojureScript: Great!

Developer: Is there a nice library for that?

ClojureScript: Sorry, you’ll need to stick to JavaScript or Google Closure. I hope you love callbacks and have brushed up on your interop skills.

Developer groans.

Even later:

Developer: Now I’d like to write a command line utility. I love Clojure, but its overhead is just too big. Can I use ClojureScript?

ClojureScript: Absolutely!

Developer: Great. How do I get started?

ClojureScript: Well, all you need to do is learn Node.js. You’ll find your interop and callback management skills handy.

Developer: I don’t suppose there are any ClojureScript libraries that will make this much easier?

ClojureScript: Nope, you’re on the wild frontier. There are some nice Node modules, though.

Developer considers using Python instead.

Ecosystem

Of course, there is a world beyond the standard library. I can’t account for the quality of libraries, but I can comment on the quantity:

Repository Number
Clojars (Clojure)
 libraries with a dependency on ClojureScript 188
 total number of libraries 8,270
CPAN (Perl) 129,130
Maven Central (Java) 70,518
NPM (JavaScript/Node.js) 57,443
Pub (Dart) 690
PyPI (Python) 39,573
RubyGems.org (Ruby) 69,863

Both ClojureScript and Dart have far fewer libraries than other, more established, languages. It seems that Dart does have more native libraries available than ClojureScript, and both can take advantage of JavaScript libraries (like those from NPM). It’s hard to tell which language has true edge in terms of ecosystem.

Tooling

Dart ships an all-in-one package, Dart Editor, that includes:

  • The Dart SDK,
  • A specialised build of Eclipse specialised for Dart (the Dart Editor), and
  • Dartium, a special build of Chrome that includes the Dart VM.

Additionally, there is Dart support as plug-ins for:

  • IntelliJ IDEA and WebStorm
  • Eclipse
  • Emacs
  • Sublime Text 2
  • Vim

I tried the IntelliJ IDEA plug-in, and it seems to be largely on par with Dart Editor, including features like static analysis, code completion, refactoring, and debugging. I also tried the Vim plug-in, but all it does is syntax highlighting.

I believe the Eclipse plug-in is the same as what is bundled with Dart Editor. I cannot speak for the Emacs or Sublime Text 2 support for Dart.

All in all, the tooling story for Dart is pretty solid. For a beginner, there is one download that contains everything to get started. For experienced developers, there is a good chance there is some level of Dart support in their preferred tools.

ClojureScript shares much of the same tooling story as Clojure. I’m not sure what the state of getting started on Clojure is these days, but it seems like Light Table is quickly becoming a popular recommendation.

As an experienced Clojure developer with an established Clojure working toolset, I still find working with ClojureScript more difficult than it ought to be. vim-fireplace supports ClojureScript, but I could never get a configuration that gave me an entirely satisfactory REPL experience. Even when I did manage to connect to the browser, it didn’t seem like my changes were having an effect. Also, features like documentation lookup no longer worked. I’ll accept that this may all be my own fault, but in the end I was back in an edit/compile/reload loop that at times seemed painfully slow (up to about 30 seconds for a recompile).

I have used Light Table with the ClojureScript and the Om tutorials. Undoubtedly, having the instant feedback from using the REPL makes development a much more efficient and enjoyable experience.

Debugging

Although this falls into tooling, I thought I’d draw special attention to debugging. As I mentioned earlier, you can use the debugger in Dart Editor, Eclipse, IDEA, or WebStorm together with Dartium and get a pretty good experience. Dart also prepares source maps when compiling to JavaScript, easing debugging on the browser.

One common complaint about Clojure is its poor error messages. I have never felt that was the case, but I came to Clojure with a lot of JVM experience. I think that ClojureScript definitely lends credence to the notion. It’s possible to enable source map support for ClojureScript, and it helps. However, that also significantly slows down compilation speed. Given how difficult it is to figure out what actually went wrong (often just a mistyped name) from a JavaScript stack trace, I began to really appreciate the static analysis support for Dart.

Documentation

Dart has excellent documentation. There are many tutorials, articles, code labs, and examples all on-line and accessible from Dart’s home page. Many of the tutorials also don’t presume a lot of specific development experience. As an example, this is very helpful to an experienced back-end developer who is just getting started with developing web applications for the browser.

There are good resources for ClojureScript, but they are spread about the web, primarily in different people’s blog posts. The wiki on ClojureScript’s GitHub has some good links, but I found most of my resources through web searches. Additionally, many resources presume that you’re already familiar with the underlying platform, making it just a bit harder to understand what’s going on and how to get started.

Outside the browser

One of the selling points of Dart is that in addition to being able to create browser-based web applications, you can write the server in the same language. This is great; you can reuse some of the same code seamlessly in both the client and the server. Additionally, it is possible to write command line utilities and even interact with native libraries written in languages like C or C++.

This is also possible with ClojureScript. While it is possible to write ClojureScript servers that run atop Node.js, it is much more common to write the server side in Clojure. As with Dart, the primary benefit of this arrangement is the ability to share code between the server and the client. Additionally, the fact that both ends can easily speak EDN to each other helps. The trickiest part of this combination is that there are subtle differences between Clojure and ClojureScript, and you have to be careful to keep your cross-language libraries within the intersection of the two.

Integrating with Polymer

Polymer is a library built by Google on top of Web Components designed to make it easy to create and reuse bits of functionality in web pages using custom elements. It is largely JavaScript-based, but there is a port of it to Dart, Polymer.dart. I don’t have any previous experience with Polymer, but I got a nice taste of it working through the tutorials.

Working with Polymer.dart was a relatively easy experience. It, along with Polymer itself, is still in a pre-release state. It’s not quite feature-complete compared to Polymer itself, but seemed pretty solid as a whole. I felt the trickiest part of using Polymer.dart was ensuring that the two-way data binding on nested data structures worked well.

There is no equivalent library for Polymer in ClojureScript, so it’s necessary to use ClojureScript’s interop with Polymer. As a result, you can do it, but getting the data binding to work with ClojureScript is an absolute pain. A good library would go far in making working with Polymer more palatable.

Integrating with Angular

AngularJS is an MVC framework for building large web applications, and AngularDart is said to be the future of the framework. AngularDart is not a strict 1:1 port of AngularJS. It works a bit differently and has been said to be ‘Angular reimagined’.

This is my first exposure to Angular of any flavour, and my impression is that it is a fairly neat framework. I enjoyed working my way through the AngularDart tutorial, but it is clear that it is still a pre-1.0 product. It’s not so much that the library is buggy, but the developer documentation is lacking compared to other parts of the Dart ecosystem.

I have not tried much Angular with ClojureScript; I simply haven’t had the time. There are multiple efforts to make Angular and ClojureScript work better together. Given the popularity of AngularJS, I wouldn’t be surprised if a good AngularCLJS library comes about.

In conclusion, it’s still early for ports of Angular to other languages. However, given that Google is behind AngularDart and pushing it forward, I expect it to mature much more quickly.

Asynchronous programming

Dart has a couple of key features for facilitating asynchronous programming:

  1. Futures, a way of performing work asynchronously; and
  2. Streams, a way of asynchronously managing series of events.

Dart’s futures are quite similar to Clojure’s, though a bit richer. For example, there is built-in support for chaining futures and for propagating errors through a chain of futures. Dart’s streams provide a way of acting on a series of events, such as getting data from a socket or reacting to UI events. Both of these features help ameliorate the ‘callback hell’ problem that’s associated with JavaScript.

In comparison, ClojureScript has no native support for either one of these mechanisms. However, there is core.async, a powerful Clojure and ClojureScript library for asynchronous programming. With it, it is possible to write highly asynchronous code in a fashion that reads as if it were synchronous. This makes the code significantly easier to reason about. David Nolen has written a good introductory article about the power of core.async. The main downside to core.async I have run into is that it makes debugging more difficult due to the immense transformation of the code at compile time.

In the end, while I think Dart’s approach to handling asynchronous programming is fairly decent, it doesn’t have the power of core.async.

Final thoughts

Dart was designed to be a language that is easy to learn and can scale to large projects, and I think it has accomplished that goal. If someone with a background in a language like Java or C++ asked me about a language for developing web applications, I would definitely recommend that they consider Dart. With Dart, as with ClojureScript, it is possible to write both the client and the server in the same language reusing the same code. In fact, it’s probably easier in Dart than a hybrid Clojure/ClojureScript application.

Does this mean I think Dart is better than ClojureScript? In a word, no. I would still recommend ClojureScript to Lisp aficionados and adventurous programmers. Most importantly, I believe ClojureScript’s Lisp roots make it a playground for innovation. I do not think something like core.async’s go macro is possible in a language like Dart. With a working browser REPL, ClojureScript should have the same highly-interactive development experience that Clojure provides, and that makes programming a much more enjoyable and productive experience.

In the end both Dart and ClojureScript are great languages. Dart is probably the more ‘practical’ of the two, and certainly the easiest to pick up. However, ClojureScript is more powerful and, in my opinion, fun.

TrackBacks

Comments