waffle

Waffle was a weblog that ran for nine years and five days from 2003 to 2012.
The last post has been written and comments will be closed by the end of March 2012.
The author of Waffle, some guy in Sweden, also occasionally writes stmts.net.

(If anything will ever succeed or revive Waffle, it will be announced in this location, and in the feeds.)

The Long-awaited async

I’ve been wracking my mind trying to figure out how to best summarize the new async/await feature in C# 5.

A short example is this:

async Task<Tweet[]> GetTweetsOfAllFollowers(string query, TwitterAPI api) {
    var matchingTweets = new List<Tweet>();
    var followers = await api.GetFollowers(); // 1
    foreach(var follower in followers) {
        var tweets = await api.GetRecentTweets(follower); // 2
        var applicableTweets = tweets.Where(tw => tw.Text.Contains(query));
        matchingTweets.AddRange(applicableTweets);
    }
    return matchingTweets.ToArray();
}

The interesting thing about this is how it runs. It starts out at the top, naturally, and then runs to point 1. At that point, the rest of the method, right up until the end, is packaged up as a continuation. The GetTweetsOfAllFollowers method returns and posts a call to api.GetFollowers() to the current synchronization context, meaning in most cases to the message queue on the current thread.

Right after that, the method will run and kick off its own asynchronous processing. When it’s done, it’ll pick up after point 1 and continue running through the function. Each time it encounters point 2 (which may be never, if you have no followers), it’ll do the same thing all over again.

Some pre-emptive answers:

  • Task<T> is a future. It’s an existing type that can carry a result, be cancelled and be faulted (and then carry a specific exception).
  • await doesn’t block; it does just the opposite. Instead of holding the thread still until the method it awaits returns, it lets the thread do other stuff — yields, you might say — until the method it awaits returns.
  • In some cases, await can run the method inline. Let’s say GetFollowers cached its results; it’d return those synchronously and just carry on.
  • The compiler assembles the Task<T> return value for you, while you provide the actual value that it will eventually resolve.
  • The compiler contorts the code for you to work with identical semantics but remain technically able to be passed as a continuation.
  • You can use, throw and catch exceptions pretty much like you usually would. The compiler handles wrapping and unpacking the exception into the Task.
  • An async method isn’t flagged as such in the metadata; it merely is one of many methods to return a Task<T>.
  • And finally, what you all are thinking: yes, if you wrote a single-threaded program with just async/await in it instead of threads or thread pools, it’d effectively work pretty much like node.js.

For more technical detail, I think I’ll link to Lucian Wischik’s technical walk. It’s the sort of thing you love because it involves potshots at competitors in PowerPoint presentations by Microsoft employees where the Microsoft employees are right, the Microsoft employees aren’t marketing and the potshot is about clumsy technical reasoning. (That’s actually just a very minor part. Most of it is gritty details and a better explanation than I can muster. I highly recommend it.)

I will also link to Eric Lippert’s deep but riveting tease-a-thon: Continuation Passing Style Revisited, parts one, two, three, four and five.

But finally, why post about this? Isn’t this just Microsoft catching up to computer science? In a way, yes. Not only could you have written this last week in Scheme, you could have written it last week in C#, assuming you’re willing to unravel the structure of your code into what the process requires. The point is that this is the first major language that I’m aware of to actually add support to unfurl your normal iterative code into callbacks using compiler support. In a world where the conventional wisdom posits that Microsoft is perpetually ripping off a Java that can’t even seem to grow some closures, I thought they deserved some credit.

Virtue

I started out with Perl.

(Actually, I started out with Visual Basic 3, and now I’m going to wait until the laughter dies down before I attempt to lecture you all on programming. The only people who prefer coding in any dialect of Visual Basic who have some kind of programming clout also helped form Stack Overflow. You’re lucky that “prefer” is not the best description of my relationship to Visual Basic.)

Anyway, the creator of Perl, Larry Wall, famously coined the three programming virtues — Laziness, Impatience and Hubris. They apply to any programmer’s life on a daily basis.

For example, if you were to, say, create a web browser. You say that you’ll need a list of tabs and an index for the selected tab. The index can’t get out of range, weird things happen to it if you add or remove or reorder tabs, and you have to keep it synced to the UI at all times. If you say “create a tab” or “magically insert this tab that someone dragged in from another window” or “magically insert a new tab from this URL” in code, you had better create the tab and update the list. No worries, you’ve a good programmer. (This is the dark side of hubris.)

Actually, you’re such a good programmer that you’ll construct the program in such a way that these things can’t help but happen along with the corresponding coordination. Let’s just write this code. (This is the dark side of impatience.) And you write and you write and you write the code, implementing your browser bit by bit.

First you show a set of predefined tabs. Then you allow creating tabs. Then you allow removing tabs. Then you allow reordering tabs. Then you allow dragging and dropping tabs and URLs. But all this time, you patch in small variations of every theme: closing the current tab is different from closing the windows and all tabs in it, which is different from closing a window because you dragged over its sole tab to another window (and now the list needs to cope temporarily with having an empty list of tabs and no selection before it gets closed), which is different from closing a tab without switching to it by clicking its close button.

What do you end up with? Method soup. Uncoordinated and uncontrolled fiddling with magic indexes. The window skipping through several pseudo-states of incomplete tab information. All through this, you’re too lazy to fix it. (This is definitely the dark side of laziness.) Now you want to add something else, or you fix something, and you break it, and you spend ten minutes, half an hour, two hours in the debugger, skipping back and forth, setting new breakpoints, trying to keep the state and condition of the whole labyrinthine contraption in your head or jotted down at every point. You don’t know what’s going on any longer.

This story could easily have ended there.

But you’re also a programmer for a reason. You’re curious, you’re never satisfied, you don’t want to settle, you want things to improve. You wouldn’t have tried to write a new web browser if you didn’t think the others stank in one way or another, would you? Is someone else going to solve your problem for you? Hubris does your motivation good.

So let’s fix this crap. This is all about a few basic operations. You insert a tab, you move it, you remove it, you know how many of them there are, you know where a particular one is, you know which one is selected and you want to change that selection. There will always be cases where your UI may need to handle something differently: those ways we just mentioned to get rid of a tab? They should affect the UI differently, but the tab lists should be affected in the same way each time because there’s only one correct behavior. Laziness means not having to write this twice or thrice and risk getting it wrong.

So you make a class for the tab lists, and you write out the operations, and you make everything work, and you get rid of the half-states by making everything appear atomic – either you see the previous or the new state. And now the UI code just clicks into place. You can focus on what actually is different, and know that the changes to the data structure happens in the same way each time. And if you can’t formulate one of the UI changes in the terms of one of the actual operations, you’re probably doing something weird. Impatience is never having to debug unnecessarily, or spend hours jerking around your own program, looking for places where things go south.

They are not the only virtues you’ll need to have. Drive. Ambition. Curiosity. Willingness to get your hands dirty. A few things like that come to mind, and I don’t mean to disparage them or deprive them of their rightful place.

Ideals are good to have and to strive for, but you won’t always be so motivated. Larry Wall stated those things because unlike ideals, they are virtues that we all conform to even when we’re not working ideally. You’re flawed just like everyone else. He saw that those properties has negative and positive effects. Like, say, where your desire to not do meaningless work drives you to write less code that’s focused on one task and that makes more sense.

You’re going to stumble. You’re going to spend years improving, you’re going to occasionally look back at previous work in disgust, and you’re doing to walk into a new project, think you’re doing the right thing and still require a big redo in short order. The only way to not make mistakes on this scale is to write less code. It’s how you recover from these mistakes that matters.

More often than not, it’s about turning what you thought fought against you into something that works for you.

October 20 Findings

Some more observations from the Special Event.

  • iPhoto looks weird, but it also looks very polished, and I’m not even referring to the reflections. It’s interesting to think about the many small steps in UI design taken over the past few years to get us to this place. It’s not all good and it’s not all bad, but what it is is definitely a new chapter in user interfaces.

  • I still don’t get precisely why some people aren’t happy even with iMovie ’09 compared to the old iMovie (HD). The separation of video into clips is a good idea, but the need to split clips as an extra step as you edit scenes is just a dumb invention. Just select, grab and move.

  • At [41:40], the guy went “it’s kind of like an automatic spell checker for bad rhythm” — was he just selling us on the idea that bands shouldn’t play well, and that doing this sort of adjustment doesn’t violate your love for genuine music? I think he was. It’s okay by me, but it’s an interesting problem to face when people think your feature is so good it’s morally flimsy.

  • Apple won’t do a vertical touch surface [56:25] because “after a short period of time, you start to fatigue, and after an extended period of time, your arm wants to fall off”. I still believe that Mac multi-touch has a destiny beyond trackpads, but that it’s as part of a big area around the palmrest beyond just the trackpad. They could do on MacBooks what they can’t do on iPhones; provide an equal surface area with a big “hand cursor” shadowing the screen. Not in Lion, but someday.

  • It’s not just me. Not even Apple’s own people can perform the multi-touch gestures on the Magic Mouse. The Magic Trackpad is the future, and the Magic Mouse feels very gimmicky.

  • The new MacBook Air actually feels tiny. To me, the curves on the previous model made it look bulky, not thin. Having a USB port barely fit right on the edge might have a psychological effect (and yes, I know that it’s thicker than just that edge). Then again, Jony Ive might simply be a witch. I think the 11″ model will fill, and fit in, a lot of stockings this year. It’s a vastly better gateway drug than the great white bore that is the plastic MacBook.

  • “The most affordable Mac ever”. Dude, the Mac mini is right there.

  • Lastly, I said even before the event that I’m ready for a MacBook Pro without the optical drive. I can deal with an external drive, even as a paid accessory; all the more to make better use of the space inside, or to simply cut down on it. If the MacBook Air really headlines an upcoming “new generation of MacBooks”, that bodes well.

Older posts »