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:
- 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.
- 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:
- Futures, a way of performing work asynchronously; and
- 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.