Monday, January 27, 2014

Half a year with Dart.

So I've been fiddle-fucking around with Dart for about half a year now. I've used it for some internal stuff, and a small open source project.

Pros:

Flexible problem solving: Aside from executing arbitrary code at top-level and serious metaprogramming (both of which I sorely miss, coming from Ruby), Dart is a very flexible language. It embraces type inference, closures, top-level methods instead of one liner abstract classes. You're not going to find any amazing stuff like RSpec, Rake, or the Rails Router coming to Dart, though: the language is simply not that flexible. But if metaprogramming / DSLs / etc aren't your thing, Dart kinda lets you barrel through.

Structured ecosystem: Dart has its own package manager, coding conventions, baked in "dartdoc" comments-as-documentation -- all the things you'd expect of a 2014 language. JS is still waffling: RequireJS or CommonJS or AMD? Bower for the client, npm for the server, and you sure as fuck aren't going to be using a single package from either source for both frontend and backend dev.

It's fast: this and the clean syntax is ultimately why I chose it for a project over JavaScript. If the mythical ES6 or Harmony had shown up that day, I would have used that instead: decent performance, more flexibility, and a clean syntax would have won out over Dart's great performance and syntax.

ES6 is Java's Project Lambda: it'll arrive several years late and JavaScript devs are going to have an aneurism when they see what the module syntax looks like.

Batteries included: they packaged pretty much everything you need to get running quickly.

Cons:

The library / parts syntax: the way Dart handles including files in a library is very bizarre. The library declaration is straight-forward: `library foo;` in your library file. Simple right? You also have to specify that a file is part of a library, too; that's fine: `part of foo;`.

Off to the races, right? Wrong. Because you have to go back to the library file again, and then add `part 'filename.dart'` as well. For every file in your project.



This is obviously for the compiler's benefit, because any human being wanting to know what file belongs to what project would just look at the source tree on the file system, like every other sane language on the planet.

This is flexibility without convenience.  You have flexibility in that you can, if having downed a six pack and are now in a drunken stupor, mix several different libraries and their implementation files in one directory and make out OK.

But you don't have the convenience of declaring all files in a directory to be part of your library, even though that's what 99% of how everyone everywhere lays out their source tree.

It's good to be flexible, but it's bad to be inconvenient.

Mirrors API: Mirrors are analogous to C#'s reflections API and Java probably has something similar. There's not a lot of affordance in the API: using `dart:mirrors` means you are going to be writing a lot of code unless you are using it at its most base, simple level.

No generators: Dart doesn't have generators, so List(...).where(...).map(...), is two iterations over a list; if your "where" returns every item in the list, you've done O(N) twice: once for the where, then once again for the map.

For a language trying to be fast, this is a weird oversight since List is heavily used (and expected to be heavily used) in public APIs everywhere.

UPDATE: Dart actually gets around this by using lazy iterables: where() returns a lazy iterable, and map() returns a lazy iterable based on the previous iterable. Thanks to +Lasse in the comments for pointing this out.

"The Editor Will Do It": Dart relies heavily on using an editor of some kind. The Dart Editor is great for starting, but its weaknesses show when you move beyond 1-2 files for a project: there's no built-in keyboard-based navigation, no source control integration, no syntax highlighting support / integration for LESS or SASS or HAML or Slim or any of the popular templating languages -- despite being built on the Eclipse platform.

A plugin exists for Eclipse, but it feels like a red-headed stepchild. I finally figured out how to import the files of an existing Dart project into my workspace by creating a "new" Dart project; when it asked me for the "project name" I just put the directory the existing files were located in, and walla, fooled the IDE I guess.

I have a licensed copy for RubyMine, and thus can use the Dart plugin, but it needs serious love.  It's obvious whoever is developing that plugin has never used it for anything other than ensuring the plugin seems to work.

Maybe it's just me, but having to stop coding and mouse around clicking the '+'s and '-'s on a file picker to find the file I want consumes an inordinate amount of time and makes it hard to keep track of what I was doing. CTRL+SHIFT+R and done, son.

In the end, Dart is still a respectable development choice. With ES6 being god knows where, doing god knows what, with god only knows how long until you can actually "use" it, Dart is a strong contender as long as you don't need that JS magic.

But goddamn do I want to use a Light Table supported language.

5 comments:

Unknown said...

Thanks for the write up!

re: library / part. A lot of us simply use one file per library. Even if you don't adopt that convention, you'd still need to use `part 'file.dart'` in the main library. Without it, how would a library.dart loaded in the browser know how to get the related parts?

Think of `part` like a `<link>` in HTML.

The Mirrors API is low-level, and is actively being cleaned up. We expect higher-level reflection libraries to appear once we sort out the low-level API and its effect on tree shaking.

I, too, would like to see stronger tooling for editors that aren't Dart Editor. Please file bugs with WebStorm / JetBrains. They are usually quick if we file bugs.

[disclaimer: I work on the Dart team.]

Unknown said...

re: library / part.

If I have to use the 'part' syntax in the entry point to the library, why do I also have to write 'part of' inside the class? I'm repeating myself for no reason! I suspect that 'part of' exists purely to help the compiler understand its current scope.

But I don't really care about that. My annoyance is the volume of lines you have to write. At the very least, the compiler should be able to support globs: part 'library_name/src/**/*.dart'

StageXL has 122 part statements, encompassing every file in its src/ directory. It's ridiculous: https://github.com/bp74/StageXL/blob/master/lib/stagexl.dart#L22

re: "A lot of us simply use one file per library"

Can you explain this to me / link to some code that does this?

I've been tooling around some open source Dart projects and haven't anyone do this yet.

I've done 1-file-1-library myself for a simple utility library, a bunch of top level functions, but I wouldn't go as far to say that this is the way I'd write every library: a bunch of classes all crammed into one file.

Thanks for the response, although I knew you were coming eventually. :)

Unknown said...

why do you need to put 'part of' ? Back in the very early days of Dart, we didn't need that. The feedback from developers was that there was no indication if the file was a library or a part. They couldn't look at the file and tell how the file fit into the app. 'part of' was added to clearly indicate to the developer.

library_name/src/**/*.dart doesn't work because how would the browser be able to request *.dart from the server? There's no concept of a resource like *.dart.

Lasse said...

Iterables in Dart are generally lazy. That means that for:

List(...).where(...).map(...)

it immediately (constant time) creates an Iterable when calling "where", and another when calling "map", but it doesn't do anything until you start iterating.

At that point, it will pick elements from the first list one at a time until one passes the "where" test, and then transform it using the "map" transformation. You can stop iterating at any time, and it won't access the rest of the list.

Unknown said...

"re: library_name/src/**/*.dart doesn't work because how would the browser be able to request *.dart from the server? There's no concept of a resource like *.dart."

That's for dartvm to handle. The current scheme is just foisting compiler / vm deficiencies onto the developer.

Perhaps you're saying that, on the client, Dart downloads one dart file at a time (based on the part syntax) instead of all files included with a package?

If so, I can see how that makes a glob syntax impossible, but that strikes me as one problem obscuring another problem.


+Lasse,

Thanks for that nugget. I knew Dart iterables were lazy, but I never considered that they would be a chain of lazy iterables like that