August 22, 2013

Await and Defer

A note on await and defer in TurtleBits.

Iced Coffeescript is now supported on TurtleBits, so two new keywords are available in the language: await and defer.

Together these keywords allow you to write a function that stops and resumes again in the middle, yielding control while waiting for some long-running task. If you like, your program can even be paused in several places waiting for several different long tasks at once. More examples below...

Waiting For Input

Here is a simple example. The function "readnum" prompts the user for numeric input and then calls a function after the user enters a number.

# Program 1: an explicit continuation function
readnum 'Width?', (w) -> write "Area is #{w * w}."

The program prints out the area of a square after a number is entered.

The function passed to read, (w) -> write "Area is #{w * w}." is playing the role of a continuation, which means that it really indicates the second step of a two-step procedure.

Await and defer allow you to generate continuation functions without writing them explicitly. Here is the same program written using await and defer:

# Program 2: continuation using defer.
await readnum 'Width?', defer w
write "Area is #{w * w}."

Program 2 does exactly the same thing as Program 1. The portion of the program after the "await" block is packaged as a continutation function that is supplied to "readnum" by writing "defer w". But the benefit is that we did not need to explicitly write the function or indent the code for the second step. Program 2 is written the way we think about it: two steps, one after the other.

Chaining Two Steps

Here is a program to compute the area of a rectangle, written without await:

# Program 3: Area of a rectangle with functions.
readnum 'Width?', (w) ->
  readnum 'Height?', (h) ->
    write "Area is #{w * h}."

And with await:

# Program 4: Area of a rectangle with await.
await readnum 'Width?', defer w
await readnum 'Height?', defer h
write "Area is #{w * h}."

Here await saves us from a doubly nested function call and lets us just write three steps. Without await, our program would have to have as many nested functions as calls to "read".

Looping versus Recursion

The benefits grow as the logic gets more complex. Here we use await within a simple loop to gather three sides of a triangle:

# Program 5: Area of a triangle with await.
side = []
for n in [1..3]
  await readnum "Side #{n}?", defer side[n-1]
s = (side[0] + side[1] + side[2]) / 2
a = Math.sqrt(s*(s-side[0])*(s-side[1])*(s-side[2]))
write "Area of the triangle is is #{a}."

Without await, recursion is necessary to express a loop. In the recursive program, the logic is unfortunately not written in the order in which it runs:

# Program 6: Area of a triangle without await.
side = []
n = 1
ask = ->
  readnum "Side #{n}?", (x) ->
    side[n - 1] = x
    if n < 3
answer = ->
  s = (side[0] + side[1] + side[2]) / 2
  a = Math.sqrt(s * (s - side[0]) * (s - side[1]) * (s - side[2]))
  write "Area of the triangle is is #{a}."

In cases like this, the "await" form of the program is definitely more readable than the recursive alternative.

Animation and Await/Defer

The other use for await is to yield control back to the system. As long as our program is busy running, the system cannot take care of basic functions such as responding to mouse clicks or rendering page elements on the screen. However, when we are paused after an await block, the system is able to process interactions.

TurtleBits defines a function "done" that triggers a call whenever running animations are complete (if no animations are in progress, it queues an immediate callback). So it useful to write "await done defer()" to yield control to the system until the turtle has stopped moving.

# Program 7: Orbit the mouse positition.
while true
  d = distance lastmousemove
  if not d > 1 then d = 1
  b = bearing lastmousemove
  speed Infinity
  turnto b - 90
  speed 10000 / Math.pow(d, 1.5)
  rt 10, d
  await done defer()

This simple "while" loop computes the distance and direction of the mouse position, orients at 90 degrees, and animates a 10 degree arc centered the mouse. The speed of the animation depends on the distance from the mouse.

Without the "await" block, this loop would repeat infinitely without pausing for the system to execute any animation, so it would freeze up the system. But the "await" block pauses the program while waiting for the system to execute the animation. The pause also allows the system to update other normal things like handling scrollbars and button presses.

So if you want to write a program that does an infinite sequence of animations, keep in mind that all the motions will be queued up. Whenever you want to check the "current" position of the turtle, you need to first use the line "await done defer()" to pause your program and wait for the animation to catch up.

Posted by David at August 22, 2013 08:57 PM
Post a comment

Remember personal info?