PencilCoder

Teacher's Guide: Array Destructuring!

Overview

This lesson introduces array destructuring as a convenient means by which to unpack information contained in arrays of known length, such as those returned from the sizexy and getxy functions. Although students will benefit from this technique, the most important aspect of the lesson is its emphasis on issues related to animation queues.

This is the first of many lessons in which students will use functions that provide information about the current state of the graphical output window. When calling such functions, attention must be paid to sprites' animation queues. The lesson builds on earlier discussions of animation queues, which centered on use of the sync function. This lesson introduces the statement array done defer() which provides greater control over the timing of execution of statements vis-a-vis animation.

More about the lesson

Animation queue

Students first learned about animation queues in the Add More Turtles! lesson, which introduced the sync function. The teacher's notes accompanying that lesson provide additional detail. An important takeaway from that lesson is that the order of statements in a script does not necessarily coincide with the order in which the animations caused by those statements appear on screen.

This lesson describes how animation queues get created, identifies the challenges that sometimes arise when writing code that depends on a current state of the animation, and provides the means to work around such difficulties. The importance of these queue-related concepts with respect to future lessons cannot be overstated. Challenges involving the animation queue will surface with increasing frequency as students write code to respond conditionally to the current state of sprites and other elements in their programs.

await done defer versus sync

The statement await done defer() instructs the browser to wait for animations corresponding to code preceding the statement to complete before subsequent code is executed and animations corresponding to that subsequent code are scheduled. Because this blocking of subsequent code and clearing of queues applies to animations for all sprites, calls to await done defer() cause all sprites to become syncronized at that point of the program, similar to what a call to sync could do. But it is important to recognize that these two expressions are fall from equivalent.

The effect of the call to await done defer() on the execution of the program is much more dramatic than any call to sync. Most importantly, at least from the standpoint of this lesson, only calls to await done defer() can be used to ensure that functions such as getxy return values the reflect the current state of animation.

Array destructuring

The function getxy (and to a lesser extent, sizexy) was selected for this lesson because it is an excellent vehicle to learn more about animation in the Pencil Code environment. However, for students to make meaningful use of these functions, they need to be able to access the individual elements of the arrays returned by them. Array destructuring provides this. This feature is particularly useful when working with output from functions that produce arrays of known size.

As the examples in the lesson illustrate, array output from getxy and sixexy can be assigned to a variable, from which elements can subsequently be assigned to variables using array destructuring. Array destructuring does not affect the original array. It simply allows the coder to assign variable names to individual elements in an array.

The lesson also illustrates that output returned from source functions can be destructured directly, e.g., [w,h] = sizexy(). This is the more common approach, not just because it requires only a single line of code, but because once the individual values have been assigned names, there is typically no subsequent need for the array that contained them.

see

see, which is an alias for the Javascript function console.log, is a valuable debugging tool when working with functions that return information about the current state of a sprite. A telltale sign of an animation queue mistake when using getxy is that the x and y values subsequently reported using see will always be 0 rather than numbers that reflect what is happening on screen. The timing of output from see to the console is also informative. Output from see statements that follow calls to to await should appear with a delay, not when the program first starts.

Notes to activities

The first three activities are designed to get students working with the screen-dimension values returned by sizexy. These challenges are designed to encourage students to explore different algorithms to accomplish similar tasks. Encourage thoughtful choice of variable names, which contributes not only to better readiability of code, but to more consistent student success. For example, for the MovetoFiller activity, the extreme x and y values are needed as inputs into moveto. Setting up variables as follows greatly simplifies working through the subsequent logic of the program:

[w,h] = sizexy()
left = -w/2
right = w/2
top = h/2
bottom = -h/2

It should be noted that use of sizexy in these activities does not require prior calls to await done defer(). This is because the size of the browser window is known at the time the program runs.

A challenging extension to the FramedImage activity involves making the frame width depend on the size of the image. To do this, students will need to be introduced to the Sprite class's width and height methods. For example, for the default turtle, turtle.width() returns 20. However, for image sprites created using the two-step process recommended in Images! (i.e., using wear), students will likely need to preceed calls to width and height with await done defer(). This arises because the dimensions of a Sprite change to take on the dimensions of the image assigned to it. Without a call to await done defer() after a call to wear, width and height will return values corresponding to the default dimensions of a Sprite, i.e., 256 by 256, which may not be valid for the sprite that is now wearing an image.

A similar extension to the StarMaker program is to draw the largest star possible in this visible window. Because students have not yet been introduced to conditional logic, they do not yet have the tools to determine programmatically which dimension is larger. A relatively straightforward solution is to introduce them to the built-in Javascript function min. (Built-in mathematical functions will be introduced formally in Mod And More!).

StarMaker is the only assigned coding activity for which students need to call getxy and thus also await done defer(). The instructions provide the essential components of the algorithm needed to produce the star so that students can focus on animation-queue-related issues.

Some of the additional activities listed below require use of getxy and therefore provide students with further opportunites to work through animation queue issues. However, these are much more complicated coding challenges, and possibly not appropriate for all students. Students will have many more opportunities to work with getxy and other features that involve the animation queue in subsequent lessons.

Additional activities

  • EvenStarMaker: The lesson introduced an algorithm for drawing a regular star with an odd number of points using two sprites, t1 and t2. We can use a slight modification of this approach to draw stars with an even number of points. As before, the "target" sprite, t1, should identify points that the second sprite, t2, should move to when drawing lines. However, for stars with an even number of points, have the target sprite alternate between identifying the next vertex (using t2.rt (1/2 - (1/points))*360, radius, as before) and identifying the point on the opposite side of the circle.

    EvenStarMaker
  • TakeTwo: Make a copy of a program created as part of a previous lesson and enhance it so that the graphics fit the visible screen perfectly.

  • Use sizexy to obtain dimensions to draw an EveningSky that perfectly fits the visible screen. Use rgb codes to create a background gradient (such as from blue to black). Add randomly positioned stars and, optionally, a silhouette for the horizon.

  • TurtleTracker: While the turtle moves randomly about the screen, have two sprites in the shape of arrows track its position on the screen. The sprite that tracks vertical movements ("changes in y") should only move up and down, and the one that tracks horizontal movements should only move left and right.

  • Imagine a turtle that traces out a circle on screen. The height of the turtle will increase for a while, then decrease, then increase again, and so on. By tracking this changing height on another graph, we can trace out the pattern of a special curve, the sine graph. Sine is used in math and science to model various types of waves, such as light, sound, radio, and electricity. Code a program CircleToWaves that tracks the height of the turtle on the circle with getxy and uses this information to generate a sine curve.

Beyond the lesson

Array destructuring

Array destructuring is a convenience method, not an essential coding feature. Other means for accessing individual values of an array, and other aspects of arrays, will be introduced in later lessons, including Push/Pop! and Subscripting!

Array desctructuring facilitates swapping the values of two or more variables, e.g., [x,y] = [y,x]. This statement actually creates a temporary, anonymous array (on the right-hand side), the elements of which are then assigned to variables x and y using destructuring.

On a technical note: when using destructuring, if the array on the left-hand side (LHS) of the assignment operator is shorter than the array provided on the RHS, only the first values of the RHS array will be assigned to variables. If the LHS array is longer, then the unused variables will be assigned undefined.

Frame-based vs. queue-based animation

Using await done defer() is one of two general approaches to resolve animation queue challenges . The other approach is to switch from queue-based animation (the default in the Pencil Code environment) to frame-based animation. One can do this by setting speed Infinity or by putting code in forever loops (which run at speed Infinity). Frame-based animation will be discussed in the Forever! lesson, much later in this course. Excellent background information on Pencil Code animation is also provided in the following reference documents:

What can go wrong

Animation Queue

The most common errors that arise in this lesson involve calls to getxy that don't produce the expected values. This almost certainly owes to a failure to invoke await done defer() prior to calling getxy. For example, the following short script will always print 0, 0 to the test panel:

moveto 100, 100
[x,y] = getxy()
see x, y

Array destructuring: not for Objects

Array destructuring only works with arrays. However, some Pencil Code functions return non-array objects. For example, the expression random(position) randomly selects a location on the visible screen. However, the return value from this function is a custom Javascript object of the form {pageX: 0, pageY: 0}. Attempts to desctructure this object will fail. Another example involves pagexy. This relative to getxy also returns a sprite's location on the screen. In fact, it is used in the code that sets up the Pencil Code environment to define getxy! However, because it is expressed as an object, the output from pagexy cannot be unpacked with array destructuring.

Pedagogy

Animation queue: a valuable pedagogical feature

Thousands of coding languages exist, each with its own set of strengths, and each optimized for a particular set of tasks. Every coding language also has its shortcomings, limitations, inconsistencies, usage difficulties, and so on. Javascript is no exception. It is a ubiquitious and flexible language, but it has a relatively steep learning curve. It's syntax is moderately complex. Moreover, using Javascript in an authentic fashion quickly entails a focus on HTML and CSS as well. Novice programmers end up learning aspects of three languages simultaneously, which quickly becomes overwhelming.

Enter Pencil Code. With its engaging graphical environment and low threshold to entry, Pencil Code provides an ideal entrypoint to programming. It's choice of Coffeescript minimizes syntax-related cognitive load and its jQuery-based enhancements allow students to quickly engage in fun yet authentic coding challenges with universal appeal. But while offering tremendous scaffolding for students who are new to programming, Pencil Code is written in such a way that it never obstructs students' continued growth and development as coders. Unlike other coding platforms designed for novices, Pencil Code is not a closed system. It gives the user complete, unfettered access to the full range of web-programming languages and associated libraries, in one easy-to-use front end. This doesn't just permit, but rather facilitates students' continued growth and development, without bound.

As with all languages, the design of Pencil Code involved tradeoffs. Opting to make Pencil Code an open system did not come without somes costs, which is perhaps most evident with issues involving the animation queue. Simply put, animation queue complications can be a frustration for students. However, it is a mistake to see these more challenging aspects of Pencil Code as a weakness or a deficiency. For students, grappling with animation queue issues actually highlights the important fact that regardless of the language we use, we will always have to work around such challenges.

This is what the much-touted goal of computational thinking is all about: expressing problems and their solutions in ways that a computer could also execute. Such work will always involve challenges such as those posed by the concept of the animation queue. Scaffolding is appropriate, but ultimately students need to get past the training wheels. We don't have to wait until they are graduating from high school to do this. Students don't learn how to develop true computational thinking skills if we sheild them too much. The key ingredient is to understand what is happening and why, and not simply guess and fumble about—which is what this curriculum is all about.

Technicalities

Why the animation queue?

Javascript is designed to run code immediately. As a simple rule, browsers do not allow code to block and wait for something on the screen to finish. The way to schedule animations in a browser is to plan them all in advance, and then set up a timer that plays the animation later. That is, Pencil Code (by default) actually runs all the code that sets up an animation at once before the animation begins, then it displays the animated results after the program has been fully processed by using animation queues. Because all the code is run at once in the beginning, some statements may appear to fail, but they are actual just running in the wrong place. Put another way, the difficulty is that these statements execute based on their position in the script, rather than at a desired point in the animation.

To work around this problem, Pencil Code provides a solution that derives from Iced Coffee Script, await done defer(). This statement tells CoffeeScipt to completely carry out the animations listed in all queues prior to moving past that point in the script and evaluating subsequent statements. In so doing, Pencil Code enables the program to process position-in-script-based functions (getxy, direction) when we want them processed, i.e., after sprites have moved.

done

As a precursor to investigating the full statement await done defer(), consider first done in isolation. done takes a single function as an argument, and delays processing that function until the entire animation has been completed, including animations resulting from lines of code following the call to done. (done is an example of a callback function, the topic of a later lesson.)

In the following example, ht is passed to done. As a result, the last thing that happens in the animation generated by this program is that the turtle disappears.

A limitation of done is that it can accept only a single function, and that function cannot accept arguments. Thus, we cannot pass to it a function call such as fd 100 and we also cannot pass multiple statements, at least not directly. To get around these limitations, it is typical to pass user-defined functions to done. User-defined functions will be the focus of a number of lessons beginning with Custom Functions!. Fortunately, for the purposes of this exposition, it is possible to code a user-defined function anonymously (i.e., without assigning it a name) using a straightforward and intuitive syntax involving the arrow operator, ->. This technique will be used below without further explanation. For more details, refer to the Anonymous Functions! lesson.

The following program illustrates use of an anonymous function with done. It is functionally equivalent to the preceding example. Note that dot red, 25 was include in the anonymous function to illustrate that it can include multiple statements. The code to draw the red dot could have been left at the end of the script, and the program would still function the same.

Because done doesn't process code passed to it until after all animation has been completed, it is an effective tool to resolve many of the animation queue timing problems, such as those that arise with getxy. This is illustrated in this example, which draws a square, correctly reporting the coordinates of the corners along the way. However, as the example suggests, use of done in this fashion can quickly become cumbersome, requiring successively nested custom functions. And there are other nuances to consider as well, such as illustrated in this script and this script.

Decyphering await done defer()

To facilitate calls to done and simplify our coding, we can call done in conjunction with a pair of functions from Iced CoffeeScript, await and defer. These functions work in tandem, as follows:

await pauses execution of subsequent statements in the program (a behavior called blocking) until done has completed its task. Because of the block, no new animations can be added to the queue, and those that are already in the queue run their course. When all animations are finished, done executes the function passed to it as an argument. This function, which is generated by the function call defer() (i.e., not defer itself*), signals to await to unblock and allow the browser to recommence processing subsequent lines of code.

To recap, a call to await done defer() sets the following in motion:

  1. await blocks the processing of subsequent lines of code.
  2. defer() generates a function that is passed to done as an argument
  3. currently running processes (i.e., animations) continue until they finish
  4. the function that was passed to done is executed, which signals to await to stop blocking.
  5. subsequent lines of code are processed, which may include setting up and running additional animation queues

* In the discussion above, explicit consideration of the parentheses in await done defer() can help decypher the expression: await(done(defer()). As always, execution of nested functions processes from the inside out. First, defer() is called, producing a callback that is passed to done. done is then immediately invoked. However, the code in the callback passed to done does not run until after the animation queue is empty. Importantly, this does not block subsequent expressions from executing. Therefore, await executes. It blocks Javascript from executing subsequent lines of code until it receives a signal, which is created by defer when it finally executes.