Pencil Code Project Ideas

Project: Await for Beginners

This project is to add ECMAScript-7-style "await" syntax to Iced Coffeescript.

If you're interested in doing this project, take a crack at the pre-project, and submit a link to your fork as part of your project proposal.

Motivation

Pencil Code is about empowering novice coders, and one of the classing beginner coding projects is Guess My Number.

Here it is, in CoffeeScript (together with the Pencil Code library for random and write):

secret = 1 + random 100
for count in [5..1]
  write "Guess my number! #{count} guesses left."
  g = prompt 'Your guess?'  
  if g > secret then write "Nope, smaller!"
  if g < secret then write "Nope, bigger!"
  if g is secret
    write "You got it!"
    break
write "The number was #{secret}"
Program 1: Easy. But - Prompt? Really? (try it here)

Should the first thing we teach students be prompt? That's terrible - prompt is unlike anything else in Javascript. It blocks the thread and pops up a window. You cannot customize it to make, say, a "nubmers only" input, and when it pops up, it prevents students from interacting with the page, invariably covering up messages like "bigger" or "smaller" that the student has printed on the screen. Try using it, pretending to be a student. It's OK, but not great.

But if we create a custom readnum function with a better UI for students to use, we cannot block the UI thread, so to wait for the user to finish entering an answer, readnum must use a callback argument. With a callback, Guess My Number would look like this:

secret = 1 + random 100
count = 5
startloop = ->
  write "Guess my number! #{count} left."
  readnum 'Your guess?', finishloop
finishloop = (g) ->
  if g > secret then write "Nope, smaller!"
  if g < secret then write "Nope, bigger!"
  count -= 1
  if g is secret
    write "You got it!"
    count = 0
  if count is 0
    leaveloop()
  else
    startloop()
leaveloop = ->
  write "The number was #{secret}"
startloop()
Program 2: Callbacks? What happened to my loop? (try it here)

This program is obviously much harder to understand and write. Using a callback means the programmer needs to plan and create two or three functions that call each other. It is totally out of reach of a first-time programmer.

And await solves this.

Using Iced Coffeescript

The Coffeescript world currently has an implementation of await that addresses this problem, and it is close, but not quite simple enough for classroom use. The implementation was done by Max Krohn in his fork Iced Coffeescript (ICS), and it lets the program look like this:

secret = 1 + random 100
for count in [5..1]
  write "Guess my number! #{count} guesses left"
  await readnum 'Your guess?', defer g  
  if g > secret then write "Nope, smaller!"
  if g < secret then write "Nope, bigger!"
  if g is secret
    write "You got it!"
    break
write "The number was #{secret}"
Program 3: using Iced Coffeescript Today (try it here)

The ICS compiler automatically pivots Program 3 so that it turns in to Program 2. This transformation is called the "Continuation Passing Style" transformation; it is a classical functional programming concept.

Max Krohn explains his brilliant ICS implmentation.

Pencil Code uses Iced Coffeescript to allow beginners wait for asynchronous operations without writing callback functions.

Unfortunately, the ICS syntax confuses beginners. In our attempts to teach with it, we have found that beginners do not understand that defer g creates a continuation function that is being passed. For example, they cannot figure out "why they need a comma" before the word defer if it's a second argument.

A Simpler Syntax for Beginners

Teachers have suggested that a better syntax would be to eliminate defer and use this:

secret = 1 + random 100
for count in [5..1]
  write "Guess my number! #{count} guesses left"
  g = await readnum 'Your guess?'  
  if g > secret then write "Nope, smaller!"
  if g < secret then write "Nope, bigger!"
  if g is secret
    write "You got it!"
    break
write "The number was #{secret}"
Program 4: The target beginner-friendly syntax.

Interestingly, this syntax is exactly what is being proposed for the next version of Javascript in the ECMAScript 7 committee. In ES7, await will be an operator that causes flow to wait for a promise to finish.

In order to use an ES7 await, you cannot use a regular function that hakes a callback function as an argument. Instead, you need to use a standard callback convention: you need to return an ES6 Promise. Promises are the future of asynchronous code; both ES6 and ES7 encourage everybody to use the same pattern.

Another interesting fact is that the proposed syntax can coeexist with existing Iced Coffeescript syntax: if an await block does not contain a defer expression, then we can safely assume that it is an ES7-Promise-style await. A very rough prototype showing a working syntax that is unabmiguous for Coffeescript's LALR(1) parser has been done in a branch of ICS here. It limits Promise-style awaits to be used in top-level assigments.

The Project

This project is to implement the ES7-style await syntax in ICS for use by begnning students. The branch above has a rough start at an implementation. Several things are needed beyond the code above:

  1. Learn the ICS codebase and improve the prototype fork: wrap the use of promises to provide understandable run-time errors when a Promise-style await is misused (for example if a student uses await on a non-asynchronous function).
  2. Add async function support. When a promise-await is used in a function, the function should be made to be an ES7-style async function, returning a Promise instead of an immediate result. This is analogous to the ICS autocb feature, and could be implemented as an adaptation of that existing code. You might decide to do this transformation automatically, or alternately introduce new operators for async functions, for example ==> or -->.
  3. Add support for async exceptions. When an async function is generated, it should catch and propagate exceptions by resolving the returned promise as an error.
  4. Productionize the implementation. Tests are needed; an interface to the parser is needed that doesn't mutilate the parse tree during parsing. (The prototype messes up the parse tree during parsing, and it should done in a separate phase.) The diff to mainline ICS should be minimized, so the patch can be maintained.
  5. Finally, if all goes well, we would work with Max Krohn to merge the new syntax with mainline ICS.