Tapestry Training -- From The Source

Let me help you get your team up to speed in Tapestry ... fast. Visit howardlewisship.com for details on training, mentoring and support!

Tuesday, August 14, 2012

CoffeeScript Cautions

Over the last couple of months, I've coded nearly all my client-side code in CoffeeScript. I prefer CoffeeScript to JavaScript ... but there are a few things to watch out for. I thought I'd share some practical experience, rather than the more typical hype for CoffeeScript or the reactionary vibe against it.

My biggest problems with CoffeeScript has been with indentation, which is no surprise, given the basic value proposition of the language. There are places where I've accidentally deleted a single space and completely changed the meaning of my code.

Here's an adapted example. We'll start with the correct CoffeeScript source code, and JavaScript translation:

Now, notice what happens when we delete a single space.

The change in indentation confused the CoffeeScript compiler; it ended the call to View.extend() and created a new expression for an object literal that is created and thrown away.

And remember, this isn't hypothetical; I really did this, and started seeing very odd errors: undefined properties and events not getting triggered. It took me a bit of time, with the debugger, and adding console logging, and eventually some viewing of the generated JavaScript, to realize what had happened.

Part of my solution to this class of issue, in Emacs, is to automatically increase the font size when editing CoffeeScript files. I may have to consider a switch from spaces to tabs as well, something that I've never considered in the past. I've also noticed that some IDEs, including IntelliJ, draw a vertical line to connect code at the same indentation layer. I don't have enough Emacs hacking experience to accomplish that, but I hope someone does.

Another problem I've hit is with function invocation; CoffeeScript style is to omit the parenthesis following the function name, and just immediately start listing function parameters:

element.setAttribute "width", "100%"

However, if your API allows chaining, you may be tempted to:

element.setAttribute "width", "100%".setAttribute "color", "blue"

Alas, the .setAttribute binds to the immediate value, the string:

element.setAttribute("width", "100%".setAttribute("color", "blue"));

One option is to enclose the first call with parenthesis:

(element.setAttribute "width", "100%").setAttribute "color", "blue"

I want to like that option, as it stays consistent with the normal format for function invocation; it just captures the result for chaining. However, each additional setAttribute() call will require an additional nesting layer of parenthesis. This leaves the following as the solution:

element.setAttribute("width", "100%").setAttribute("color", "blue")

And now we are stuck having to parse two different syntaxes for function invocation, depending on some very specific context.

Another issue is that, once you start using the standard function invocation style, which doesn't use parenthesis, you can easily get tripped up: Here the is null bound tightly to the "div" string literal; the result (true or false) was passed to the find() method, rather than the expected "div".

Because of these subtleties, I seem to find myself using the live CoffeeScript to JavaScript preview to check that my guess at how the JavaScript will be generated is correct. Preview is available on the CoffeeScript home page, and also as the CoffeeConsole plugin to Chrome.

What's interesting is that the above concerns aside, most of the time my first pass at the CoffeeScript does produce exactly what I'd expect, making me feel silly to waste the time to check it! Other times, I find that my first pass is actually too verbose, and can be simplified. Here is an example from some code I've written for Tapestry, a simple DSL for constructing DOM elements on the fly: This is invoking the builder() function, passing a string to define the element type and CSS class, then an object that adds attributes to the element, and lastly some additional values that define the body of the element: an array to define a nested element, and a string that will become literal text. This is converted to JavaScript:

Turns out, we can get rid of the curly braces on the objects without changing the output JavaScript; CoffeeScript is able to figure it all out by context:

Even getting rid of the commas is allowed. The commas are equivalent to the line break and indentation that's already present:

And that's where I start really liking CoffeeScript, because quite often, the code you write looks very close to a bespoke DSL.

Then there's the debugging issue; CoffeeScript scores poorly here. First of all, the code has been translated to JavaScript; line numbers at execution time will no longer line up with source CoffeeScript at all, so the first and most useful bit of information in a stack trace is no longer useful. It's very easy to glance back and forth to match the generated JavaScript back to source CoffeeScript, but it still takes some effort.

Of course, in a production application, you'll likely have minimized your JavaScript, which mangles your JavaScript and your line numbers far more than the CoffeeScript compilation does!

The other weakness is that all functions in CoffeeScript are anonymous; this takes another useful bit of information out of stack traces: the local function name. This leaves the only way to map from compiled JavaScript back to source CoffeeScript as involving a lot of processing between your ears, and that's not exactly ideal, given everything else you have to mentally juggle.

Lastly, I've heard of people who think CoffeeScript is only for programming using objects and classes. I find this quite odd; it's nice to have some optimized syntax for classes in CoffeeScript even if it comes at some price (the generated JavaScript can become quite verbose adding runtime support for the syntax that allows super class methods and constructors to be invoked); however, far beyond 95% of the code I write is purely functional, with what state that's present captured in closures of local variables, they way it should be.

I like Scott Davis' summary: CoffeeScript will make you a better JavaScript programmer. It is, largely, JavaScript with better syntax, plus a number of new features. You should never think of CoffeeScript as an alternative to understanding the occasionally ugly nuances of JavaScript; instead I think of it as a "have your cake and eat it too" situation. I certainly don't consider it noobscript, the way many in the Node.js community seem to.

At the end of the day, the translation from CoffeeScript to JavaScript is usually quite predictable, and turns into the JavaScript you probably should write, but often would not: for instance, JavaScript that always evaluates inside a hygienic function, or where every variable is defined at the top of its containing block.

My final take is that you are trading the very rough edges present in the JavaScript language for some sensible tradeoffs, and the occasional hidden "gotcha". I think that's a completely reasonable trade, and will continue to write as much as I can, client-side and server-side, in CoffeeScript.

Wednesday, August 01, 2012

Tapestry 5 Book by Igor Drobiasko

Is the one thing holding you back from embracing Tapestry the lack of a current book? Well, that excuse is finally going by the wayside; Igor Drobiazko, a Tapestry contributor (second in activity only to me) is getting ready to finish and self-publish his book on Tapestry 5.3: Tapestry 5: Rapid Web Application Development in Java

Want early access to the book? Join his funding campaign at indiegogo.

Igor hit his goal on day 1, but for as little as $25 you can get access to at least 400 pages of content as soon as the campaign ends, and the complete book when it's ready. You can also help assure that the book will be updated for the great improvements already cooking up in Tapestry 5.4.

More async: using auto() for parallel operations

One of the straw men that people often cite when discussing event-driven programming, ala Node.js, is the fear that complex server-side behavior will take the form of unmanageably complex, deeply nested callbacks.

Although the majority of the code I've so far written with Node is very simple, usually involving only a single callback, my mental image of Node is of a request arriving, and kicking off a series of database operations and other asynchronous requests that all combine, in some murky fashion, into a single response. I visualize such a request as a pinball dropping into some bumpers and bouncing around knocking down targets, until shooting out, back down to the flippers.

Here's an example workflow from the sample application I'm building; I'm managing a set of images used in a slide show; so I have a SlideImage entity in MongoDB (using Mongoose), and each SlideImage references a file stored in Mongo's GridFS.

When it comes time to delete a SlideImage, it is necessary to delete the GridFS file as well. The pseudo-code for such an operation, in a non-event based system, might look something like:

Inside Node, where all code is event-driven and callback oriented, we should be able to improve on the pseudo-code by doing the deletes of the SlideImage document, and the GridFS file, in parallel. Well, that's the theory anyway, but I'd normally stick to a waterfall approach, as tracking when all operations have completed would be tedious and error prone, especially in light of correctly handling any errors that might occur.

Enter async's auto() function. With auto(), you define a set of tasks that have dependencies on each other. Each task is a function that receives a callback, and a results object. auto() figures out when each task is ready to be invoked.

When a task fails, it passes the error to the callback function. When a task succeeds, it passes null and a result value to the callback function. Later executing tasks can see the results of prior tasks in the result object, keyed on the prior tasks's name.

As with waterfall(), a single callback is passed the error object, or the final result object.

Let's see how it all fits together, in five steps:

  • Find the SlideImage document
  • Open the GridFS file
  • Delete the GridFS file
  • Delete the SlideImage document
  • Send a response (success or failure) to the client

The granularity here is partly driven by the specific APIs and their callbacks.

The code for this is surprisingly tight:

auto() is passed two values; an object that maps keys to arrays, and the final callback. Each array consists of the names of dependencies for the task, followed by the task function (you can just specify the function if a task has no dependencies, but I prefer the consistency of each entry being an array).

So find has no dependencies, and kicks of the overall process. I think it is really significant how consistent Node.js APIs are: the basic callback consisting of an error and a result makes it very easy to integrate code from many different libraries and authors (I think there's a kind of Monad hidden in there). In the code, the callback created by auto(), and passed to find, is perfectly OK to pass into findById. It's all low impedance: no need to write any kind of shim or adapter.

The later tasks take the additional results parameter; results.find is the SlideImage document provided by the find task.

The remove and openFile tasks both depend on find: they will run in no particular order after find; more importantly, their callbacks will be invoked in no predictable order, based on when the various asynchronous operations complete.

Only once all tasks have executed (or one task has passed an error to its callback), does the final callback get invoked; this is what sends a 500 error to the client, or a 200 success (with a { "result" : "ok" } JSON response.

I think this code is both readable, and concise; in fact, I can't imagine it being much more concise. My brain is starting to really go parallel: part of my brain is evaluating everything in terms of Java code and idioms while the rest is embracing JavaScript and CoffeeScript and Node.js idioms; the Java part is impressed by the degree to which these JavaScript solutions eschew complex APIs in favor of working on a specific "shape" of data; if I was writing something like this in Java, I'd be up to my ears in fluid interfaces and hidden implementations, with a ton of code to write and test.

I'm not sure that the application I'm writing will have any processing workflows significantly more complex than this bit, but if it does, I'm quite relieved to have auto() in my toolbox.