Byte Works / Ordering / Apple IIGS / Apple ][ / APDA / HyperStudio / Books / System Software
Multiplication shows you how to create a classic drill program for practicing multiplication tables. This includes creating a list of multiplication problems in random order, as well as keeping track of how many problems are missed, and even which ones. After the first pass through the table, the student is asked to try any problems he missed again; this repeats until all problems have been answered correctly. The stack also keeps track of the elapsed time. While you might not want to actually use a timer for a multiplication test, the techniques you see in the timer can be used in lots of other applications. Along the way you'll learn how to use HyperLogo to draw text directly on the screen, giving you complete control over fonts and where characters are placed.
The ideas in this stack can be used in all sorts of stacks. The obvious way to reuse the stack is to create similar drills for addition, subtraction and division. You can use the scoring system with other kinds of tests, too, especially the mechanism for repeating questions that were answered incorrectly.
Copyright 2000, Byte Works, Inc.
You may read or print this page for your own use in any way you like, but it cannot be reproduced for anything except your personal use without the written permission of the publisher. Please feel free to point other people to this resource so they can get their own copy!
The stacks and scripts that accompany this project are public domain. You may use the scripts any way you like, including using them in your own HyperStudio projects.
Download the Stack
In addition to being a practical replacement for flash cards, this stack teaches you several key ideas.
There are two copies of the Multiplication stack in the download. One has the completed stack, while the other has everything but the scripts. In a moment we'll open the incomplete stack and start adding scripts, but first, take a moment to look at the completed stack.
Open the stack MultComp.stk.
The stack runs on a single card. Press the button Start to begin the drill. A multiplication problem will appear in the left box; answer the problem by typing the correct number and pressing return. Here's a few points you should be sure to notice:
You probably don't want to go through all 100 questions. Hold down the Apple key and press the period key (Macintosh) or press Esc (Windows) to stop the script early.
Now that you have a clear idea what the stack is supposed to do, we'll get started on implementing the script. This process will take several important steps.
Open the stack Mult.stk. This stack has all of the visual elements you need so you don't have to spend time creating the graphics, buttons and text objects. We'll add scripts to this stack to finish the project.
You've probably heard that programmer's are supposed to plan their programs before writing them. Several years ago, flow charts were popular, and you still see them occasionally today. Even more common among programmers is a kind of free-flowing English-like version of the program called pseudo-code; it can be very detailed or rather loose, depending on the situation. For short programs, if you look close, you may notice a programmer skipping these steps, but writing a lot of comments or creating procedures that have nothing in them, coming back to fill them in later. If you see an experienced programmer writing a script like this one, you might even think he is skipping the whole step of outlining the script&emdash;but you would be wrong.
The fact is, successful programmers always outline their programs. It may not look like it, just like it may seem that an experienced writer is skipping an outline when they write a letter. But the writer has still organized the major features before sitting down to write. The writer knows who the letter will be written to, essentially what they will say, and if there is any information needed to complete the letter, it's usually close at hand. The less experienced the writer is, the more obvious the preparations are likely to be.
As we organize this script, you may feel like it's a step the "real" programmers would skip. You're wrong. We really do this stuff, but for short programs, we do it in our head. When you have decades of programming experience, you will, too! For now, though, take the time to organize the script carefully before you start to write. Any planning you do before you start to write will pay off handsomely when you put fingers to keyboard.
We'll start with a broad outline that ignores the details, but builds a frame we can flesh out piece by piece. This is a technique known as stepwise refinement, or top-down programming. It's a very effective problem solving technique for any complicated task.
Our script will present a total of 100 multiplication problems to the student in a random order. The first order of business will be to create a list of the 100 problems and scramble the list so it is in a random order.
Once we have a list of problems, we'll present the problems one by one until all have been presented.
Putting this into pseudo-code, our program does the following:
1. Set up a random list of 100 multiplication problems 2. Present the problems and check the answers
In order to plan the script itself, we'll need to take a moment to figure out just what that list of problems will look like. There are several ways to set up a list of math problems. An easy and obvious one is to treat each problem as a pair of numbers. The problem "4 x 6" is the pair of numbers 4 and 6, stored in a HyperLogo list, [4 6]. The complete set of multiplication problems is a list of these problems, like this:
[[4 6] [9 2] [0 7] [5 3]]
We'll store this list of problems in a global variable called probs. Of course, the real list of problems has 100 problems in it, not four, but you get the idea.
The first step is detailed enough for now. You may not see exactly how it will be done, but the step is reasonably well described. We know we want to create a list of problems like the one shown above, with the problems in random order. The second step needs some work, though. There are several sub-steps needed to accomplish the second step.
First we'll need to set up some variables. We're keeping track of the number of correct answers and the number of incorrect answers. We'll need to create variables for these values and set them to zero. We're also remembering any problems that were missed so we can present them again later, so we'll need a variable to hold these missed problems, and it should be set to an empty list.
This stack uses a timer. The next step is to set up the timer.
Finally, we remove each problem from the list of problems, one at a time, and present the problem to the student. We'll leave the details of how that's done for a later step.
Once we're done, the last step is to tidy up. We'll erase the last problem from the problem area and delete the script from memory.
Putting this into pseudo-code, the process looks like this:
1. Set up a random list of 100 multiplication problems 2. Present the problems and check the answers A. make correct 0 B. make incorrect 0 C. make missed the empty list D. set the correct answer count field to 0 E. set the incorrect answer count field to 0 F. start the timer G. for each problem a. present the problem H. If any problems were missed then a. repeat 2.G. for the missed problems I. erase the background J. erase the script
With a detailed, carefully thought-out plan in place, the script for our stack practically writes itself. The fact is, we've done all of the hard work already! Here's the script that comes from this pseudo-code.
; Set up the list of problems InitializeProblems ; Set up the counts of problems missed and solved Make "missed [] Make "correct 0 Make "incorrect 0 SetFieldText [] "correct "0 SetFieldText [] "incorrect "0 ; Set up the global variables used to position the problem on the screen Make "background [-232 150 -39 -146] ; Initialize the timer Make "startTime Ticks ; Present the problems While not EmptyP :probs [DoProblem First :probs Make "probs ButFirst :probs if EmptyP :probs [Make "probs :missed Make "missed []]] ; We're done: erase the last problem and clean up the workspace EraseRect :background ErAll
Looking carefully, it might seem like this script is cheating a bit. After all, what does the procedure InitializeProblems do? The answer may surprise you. At this point in the process of developing the script, we really don't care! It's a detail we can take care of later. As long as we are confident that we know how the script will work, we really don't need to go into more detail yet.
The next few lines are almost a direct translation from the pseudo-code to HyperLogo. They set up some variables, initialize their values, and set the value displayed in the text objects. There is an extra variable here, background, which is set up to correspond to the white area where the problem will be drawn. We're looking ahead a bit here; this is a value we need to erase the problem area, which is step 2.I, but it's also a value we'll use in some of our procedures. How were we supposed to know we would need it? Well, an experienced programmer might anticipate the need, but in truth, it's usually something you see as a script develops. On the first attempt, you might just write the actual rectangle in the ERASERECT line, like this:
EraseRect [-232 150 -39 -146]
Later, when you discover that you're using the same values elsewhere, you would create a variable to store the rectangle in one place. As a matter of fact, that's what I did when I wrote this script.
We use a variable to store the initial time, and call a procedure we'll write later to figure out what the start time actually is.
The major effort, and the part we'll take the longest to examine, is the loop that presents the problems and handles incorrect answers. HyperLogo scripts look pretty crummy when they are written all on one line. Let's reprint the line in a way that's easier to read. You can even run it this way in HyperStudio 4.0 and beyond. If you're usng earlier versions of HyperStudio, though, keep the loop all on one line lise you see it in the original script.
While not EmptyP :probs [ DoProblem First :probs Make "probs ButFirst :probs if EmptyP :probs [ Make "probs :missed Make "missed [] ] ]
That's better! It's easy to see now that the line does something until the variable containing the problems is empty. The second line,
DoProblem First :probs
calls a procedure to present the problem to the student, get the answer, check it, and update the timer. We'll write that procedure later.
After calling DoProblem, we've dealt with the first problem in the list of problems, so the next step is to remove the first problem from the list. This general technique of looping while there is something in the list, feeding the first item to a procedure, then removing the first item from the list is one of the fundamental structures in Logo programming. It's the correct way to handle any situation were we want to process each item in a list, one at a time.
The if statement checks to see if we've finished our first pass through the list of problems. If so, it copies the contents of a the global variable missed into the variable probs. This is how our script handles the task of repeating any problems the student missed.
Finally, the script erases the problem rectangle and then the script itself. The last step isn't really necessary, but it is tidy: When you save the stack after running it, the workspace will be empty, and as a result the stack will be slightly smaller.
Obviously there is a lot of work hidden in the procedure DoProblem, but the overall flow of the script is pretty clear.
Edit the script attached to the Start button and type what we have so far.
In this step we will continue the process of stepwise refinement by writing the procedures that the script calls. These procedures should appear in the same script window as the script from the previous step, but they should come first. That's because HyperLogo runs the script from top to bottom, and it needs to know what the procedures are before you start using them! If you put the procedures after the lines from the previous section, you'll end up using the procedure before it is declared, and that will definitely cause problems.
The first procedure our script calls is InitializeProblems. The job of InitializeProblems is to set up a list of 100 multiplication problems. The list of problems needs to be in alphabetical order. There are two basic steps needed to accomplish this goal:
1. Create a list of 100 multiplication problems 2. Randomize the list of problems
Here's the procedure we'll use to accomplish these steps.
TO InitializeProblems ; Set up the list of problems. Local "i Local "j Make "probs [] Make "i 1 Repeat 10 [Make "j 1 Repeat 10 [Make "probs FPut (List :i :j) :probs Make "j :j + 1] Make "i :i + 1] Make "probs Randomize :probs END
The easiest way to understand this procedure is to start from the inside and work our way back out. The command that actually creates a multiplication problem is
(List :i :j)
This command creates a list with two elements, the contents of the variable i followed by the contents of the variable j. If the variable i contains 4, and j contains 5, the list this command creates will be [4 5].
We'll leave the question of how the variables get their values alone for a moment. For now, let's address the issue of what we do with the list created by the List command. We want to add this list to the contents of the list of problems, which we're storing in the variable probs. When we finish, probs will be a list of 100 lists, each of which will be a list of two numbers. The FPut command puts a value into a list as the lists new first element; the F in FPut comes from First. We add our new problem to the probs list,
FPut (List :i :j) :probs
then replace the original contents of probs with the new list:
Make "probs FPut (List :i :j) :probs
The rest of the rather complicated line this command is imbedded in is controlling the values of the variables i and j. The inside loop sets the value of the variable j to 1, then repeats a pair of commands 10 times. The first of the pair of commands is the Make statement we just looked at that builds the problem list. The second line is
Make "j :j + 1
This increments j. This entire sequence is wrapped in a similar loop that increments i the same way.
Looking at how this works, the first time the List statement is executed, i will be 1 and j will also be 1, so the List command creates the problem [1 1]. The inner loop executes 9 more times, adding the problems [1 2], [1 3] and so forth up to [1 10]. After adding the problem [1 10], the inner loop completes, and the outer loop increments i, then starts the inner loop over. This creates the problems [2 1], [2 2], [2 3] and so forth. This process continues through the last problem, [10 10].
The second step is really pretty easy. Starting with HyperStudio 3.1.8, HyperLogo has a command called Randomize that scrambles the elements of a list. We use that command to randomize the list of problems.
The rest of the procedure consists of housekeeping routines to create the local variables i and j and to initialize the global variable probs to the empty list.
Our stack has a timer that shows the number of seconds since the test started. This timer runs continuously until the last problem is answered correctly. We initialized this timer with the call
; Initialize the timer Make "startTime Ticks
Ticks is a procedure we are about to write that returns the number of seconds since midnight. It figures this out from HyperLogo's TIME command, which returns a list that contains four elements, like this:
[13 42 3 1:42:03 PM]
The first three elements are numbers. They are, respectively, the hour in 24 hour format, the minute, and the second. The last element is a word that contains the same time in a format suitable for printing. To get the number of seconds since midnight, we need to multiply the hour by 3600, since there are 3600 seconds in an hour; the minute by 60; and add these results to the number of seconds. Here's a procedure that does this, returning the time since midnight in seconds:
TO Ticks ; Returns the current time in seconds Local "t Make "t Time Output (3600 * First :t) + (60 * Item 2 :t) + Item 3 :t END
There is a subtle but important feature in this procedure that's worth pointing out. The TIME command is a very short one, and we wasted two lines storing it in a variable. Why not write the procedure this way?
TO Ticks ; Returns the current time in seconds Output (3600 * First Time) + (60 * Item 2 Time) + Item 3 Time END
If you try this version, you'll find out that it works&emdash;most of the time, anyway. The problem is that there is a small but finite chance the time will change between the three calls to TIME. This is more likely on slow computers. The chance isn't big in any case, but it can happen, so eventually it will. The result will be a bug report from a student. You probably won't realize why it happened, and may not even believe it. The procedure looks so innocent; if the elapsed time sometimes jumps by a minute or occasionally by an hour, it must be a problem with the computer, right? Or with HyperStudio? Or well, you get the idea. As a scripter, you have to be very careful to catch problems like this as you write the script. If something can go wrong, as with the second version of Ticks, it eventually will. Or, to put it in terms of a programming law:
The Ticks procedure gives us the time in a format we can use. Inside of the procedure that is waiting for the student to type an answer, we'll constantly call Ticks, subtract startTime from the time it returns, and we'll have the elapsed time to display on the card. This is a nice, neat package, and it makes sense to encapsulate the idea in a procedure that we'll use later. Here's a procedure that updates the elapsed time whenever it is called.
TO CheckTime ; Updates the elapsed time Local "t Make "t Ticks - :startTime if :t < 0 [Make "t :t + 24 * 3600] SetFieldText [] "time :t END
It's pretty unlikely that a student will be practicing multiplication tables at midnight, but if you look, you'll see that this procedure does take that possibility into account. If the student starts at 11:55 PM, startTime will be 86100. Finishing 10 minutes later at 12:05 AM, the time returned by Ticks will be 300, and the value assigned to t will be -85800. By adding the number of seconds in a day, we get 600, the correct value. Remember Mike's Second Law?
Of course, even this procedure can fail. What happens if the student takes more than one day to complete the test? In that case, the time would wrap back around to 0. There does come a point when something is so unlikely, and the potential harm caused by the error so slight, that it's worth accepting the problem rather than trying to fix it! Of course, the point where you draw this line is rather different for a simple multiplication stack you'll use with a few students and a commercial program. At least, we hope it is!
DoProblem is the procedure that actually does the real work of presenting one multiplication problem to the student and waiting for an answer. There are three steps in this process:
1. Draw the problem 2. Get and answer from the student 3. Check the answer
There are two approaches to drawing the problem on the screen. The most obvious is to use the same technique to draw the problem that we've used to display the time. We could create three text fields, one for each of the two numbers we want to multiply and a third for the answer, and let the student type the answer in the bottom text field. I've seen several people write a stack like this one over the years, and using text objects is the approach they always use.
Unfortunately, there are two serious drawbacks to this approach. First, the cursor must be in the answer box or keyboard characters will either be ignored or show up in the wrong place. Second, there is no way for your script to respond when the student presses the return key, so you will have to create a separate button that is pressed once the answer is entered. This slows the student down, and it's unnatural.
To avoid these drawbacks, we're going to forego the use of text objects and draw the problem directly on the card using HyperLogo's PRINT command. Here's an outline of the process.
1. Draw the problem A. Erase the old problem B. Set the font C. Determine the height of one character D. Print the first number E. Print x followed by the second number F. Draw a line under the second number
Here's the first part of the DoProblem procedure. It shows the HyperLogo code to accomplish these tasks.
TO DoProblem :prob ; Do one multiplication problem ; Erase the problem area EraseRect :background ; Draw the problem SetFontFamily 'Times' SetFontSize 72 SetForeColor 4 Make "fInfo GetFontInfo Make "height (First :finfo) + (Item 2 :finfo) + (Last :finfo) PenUp Make "x :probX - (TextWidth (Word First :prob)) Make "y :probY - :height SetPos [:x :y] Print First :prob Make "str Word "x Last :prob Make "x :probX - (TextWidth :str) Make "y :probY - 2 * :height SetPos [:x :y] Print :str SetPenSize 6 6 Make "y :probY - 2 * :height - Item 2 :fInfo SetPos [-206 :y] PenDown SetPos [:probX :y]
SETFONTFAMILY, SETFONTSIZE and SETFORECOLOR are HyperLogo font commands that control the font used by the PRINT command and its cousins. The commands you see select 72 point (about one inch high) blue Times characters.
GETFONTINFO returns information about the current font in the form of a list. The three numbers returned in the list are the ascent, which is the height of an uppercase letter like M; the descent, which is the height of the tail of a letter like y; and leading, which is the recommended space to leave between lines of text. Adding these values together, as the script does, gives the height of one complete line of text.
Incidentally, leading is pronounced like the metal lead, not like "leading a horse." The term comes from the not-so-distant past when type was set by hand, and thin strips of lead were inserted between lines of type to create space between lines.
The next step is to draw the first number. The first task is to decide where we will draw the text, and the second is to actually draw it. In order to make it easy to move the position of the problem later, we're going to use two variables, probX and probY, which will hold the top right position of the area where we will draw the text. All of our other positions will be based on this point, so moving the point will adjust the position of the entire problem.
To get the horizontal position, we subtract the width of the text from probX. That gives us nice, right aligned text, regardless of the width of the text we're drawing. The vertical position is just the height of the text below the corner point. PENUP prevents HyperLogo from drawing a line as the script executes the SETPOS command to set the position where the text will be drawn. Finally, PRINT prints the first number in the problem.
The next few lines repeat the process for the second number in the problem. The only new element is the use of the WORD command to attach the letter x to the number, forming a single complete string so we can calculate an accurate width. Naturally, we move down two line heights from the corner, rather than one.
The last five lines draw a thick line (6 pixels thick, to be exact) under the second number.
With the problem on the screen, the next step is to wait for the student to type an answer. We'll be controlling the keyboard rather carefully. In fact, we'll only respond to a few keys; anything we don't want will be ignored. Numeric keys are accepted, of course, as is the return key and delete key. We'll also watch for the esc key, breaking out of the loop if we see it. When a number is typed, we'll erase the area where the answer will be drawn (in case the answer is more than one digit, and there is already one there), then we'll draw the answer using the same techniques used to draw the problem. Outlining the process, it looks like this:
2. Get and answer from the student A. Make ans the empty string (no characters have been typed) B. Make done false (we'll use done for an esc key exit) C. Form a rectangle that covers the answer area D. Calculate the vertical position for the answer's text E. Raise the pen F. While done is false, repeat the following: a. Call CheckTime to update the elapsed time b. While no key has been pressed 1. Call CheckTime to update the elapsed time c. Save the character typed in key d. If key is 8 (the delete key) then 1. If there is at least one character in ans then i. Remove the last character from ans ii. Redraw the answer e. If key is 3 or 13 (enter or return) then 1. Make done true f. If key is 27 (esc) then 1. Make done true 2. Make probs [0 0] (this empties the problem list so we'll exit the program) 3. Make missed [] (same reason) g. If key is a number then 1. Append the new digit to ans 2. Redraw the answer
This is a relatively complicated outline, simply because it's so long. It's like following directions to drive to some location, though. No individual step is all that complicated, but the number of steps looks a little intimidating at first. Take some time to look through the outline. Trace through the loop to see how various keys are handled. Once you understand what we're after, examine the next part of our procedure, which translates this outline to HyperLogo.
; Get an answer Make "ans " Make "done "FALSE Make "y :y - Item 2 :finfo Make "r Replace :y 2 :background Make "y :probY - 3 * :height - (item 2 :finfo) - 6 PenUp While not :done [CheckTime While not KeyP [CheckTime] Make "key ReadChar if 8 = ASCII :key [if 0 < Count :ans [Make "ans ButLast :ans PrintAnswer :r :y :ans]] [] if or (3 = ASCII :key) (13 = ASCII :key) [Make "done "TRUE] [] if 27 = ASCII :key [Make "done "TRUE Make "probs [[0 0]] Make "missed []] [] if and ((ASCII :key) > 47) ((ASCII :key) < 59) [Make "ans (Word :ans :key) PrintAnswer :r :y :ans] []]
The last line is pretty touch to read. Here it is again, this time formatted for humans instead of HyperLogo. You must type the line like you see it above in versions of HyperStudio earlier than 4.0; you can type it like you see below in HyperStudio 4.0 and later.
While not :done [ CheckTime While not KeyP [ CheckTime ] Make "key ReadChar if 8 = ASCII :key [ if 0 < Count :ans [ Make "ans ButLast :ans PrintAnswer :r :y :ans ] ] [] if or (3 = ASCII :key) (13 = ASCII :key) [ Make "done "TRUE ] [] if 27 = ASCII :key [ Make "done "TRUE Make "probs [[0 0]] Make "missed [] ] [] if and ((ASCII :key) > 47) ((ASCII :key) < 59) [ Make "ans (Word :ans :key) PrintAnswer :r :y :ans ] [] ]
As you can see, there is one new procedure here. PrintAnswer prints the contents of ans.
The last step is to check the answer. Pay special attention to all of the things we do if the answer is wrong. A test is a teaching tool, after all, so the program should help the student by showing the correct answer if the student misses a problem.
3. Check the answer A. If the last key pressed was esc 1. Exit the procedure B. If the answer is correct then 1. Add 1 to the number of correct answers 2. Display the number of correct answers on the card C. Else 1. Add 1 to the number of incorrect answers 2. Display the number of incorrect answers on the card 3. Display the correct answer in red in the answer area 4. Make a sound to alert the student 5. Wait 2 seconds 6. Add the problem to the missed list
Here's the outline translated to HyperLogo:
; Check the answer if 27 = ascii :key [stop] if :ans = First :prob * Last :prob [Make "correct :correct + 1 SetFieldText [] "correct :correct] [Make "incorrect :incorrect + 1 SetFieldText [] "incorrect :incorrect Make "ans (First :prob * Last :prob) SetForeColor 2 PrintAnswer :r :y :ans Toot 45 60 Wait 120 Make "Missed FPut (First :probs) :Missed] END
And here's the last if statement in a format you can read:
if :ans = First :prob * Last :prob [ Make "correct :correct + 1 SetFieldText [] "correct :correct ] [ Make "incorrect :incorrect + 1 SetFieldText [] "incorrect :incorrect Make "ans (First :prob * Last :prob) SetForeColor 2 PrintAnswer :r :y :ans Toot 45 60 Wait 120 Make "Missed FPut (First :probs) :Missed ]
The last procedure we need to add is PrintAnswer, the procedure we invented while writing DoProblem. PrintAnswer simply prints the current contents of the variable ans. By now, you can probably outline this procedure in your head.
TO PrintAnswer :r :y :ans ; Print a string in the answer field Local "x EraseRect :r Make "x :probX - (TextWidth (Word :ans)) SetPos [:x :y] Print :ans END
There was one detail we glossed over in all of this, and that's setting probX and probY, the location of the point that determines where the problem appears on the card. This value is used in both DoProblem and PrintAnswer, so it should be set up in a global variable. There are a couple of places where you could do this. One is inside the DoProblem procedure. I picked the main part of the program, right after the rectangle called background is set up, so the rectangle and the problem location would be together and easy to find. I figured if you were going to change one, you would be changing both, so they should be close together. Modifying that part of the main script, it becomes
; Set up the global variables used to position the problem on the screen Make "background [-232 150 -39 -146] Make "probX -80 Make "probY 130
Your stack should be complete now, but the only way to make sure is to try it. That's really what testing is all about.
There are bound to be a few mistakes at first. If you didn't type something incorrectly, you're a better typist than me! Testing the program by answering all 100 problems each time you try the program will get real old real fast, though. But remember, you're a programmer. You can change the program slightly to make testing easier. Here's one way: Add this line right after the call to InitializeProblems:
Repeat 95 [Make "probs ButFirst :probs]
This quickly dumps the first 95 questions, leaving you with just 5. After you're satisfied that things are working the way you want, delete this line and try the entire stack once.
During your testing, be sure to check the following:
Depending on your goals, you may want to teach the multiplication tables for these numbers, too. Of course, that makes 169 problems, which takes a long time. Or maybe you want to practice the problems from 2 to 9, since 0, 1 and 10 are pretty obvious. It's an easy change to make this happen.
It's pretty easy to change this script to do the other basic math drills. If you want to get fancy, have the script start by displaying one of four buttons, Addition, Subtraction, Multiplication and Division. When one of these buttons is pressed, hide all four and show the Start button, and set a global variable to indicate which operation you're using. You can change the script to look at that variable and do the proper operation. The single script you already have can easily be adapted so a single one-card stack can do all four drills!
if and ((ASCII :key) > 47) ((ASCII :key) < 59) [
While not EmptyP :stuff [ Process First :stuff Make "stuff ButFirst :stuff ]