|
BFOIT
Introduction to Programming Defining Your Own Commands |
|
This capability of naming procedures which are made up of already existing instructions is very powerful. As you are about to learn, it is a big help in constructing large programs.
For review, here are our steps in the programming process:
We are going to look at an alternative to using pseudocode.
There's a way to replace the notes and pseudocode with another
way of representing what we are thinking. We will use Logo
commands we write and a process that's called "stepwise
refinement" to create programs.
Writing our own Logo commands will get us procedural
abstraction, an
important separation of the description of what something does from
how it does it. For now, don't worry about this technical term.
Just think of it as jargon for inventing new commands that do
one thing, something that is the first thing you think of when you
hear the name you give to the command.
Procedural abstraction is the most important programming
concept you will learn!
Let's work through an example. It will give you a good feel for what I'm attempting to describe.
| Pseudocode | Logo Instructions |
|---|---|
|
draw a box each side = 100 steps |
fd 100 rt 90 fd 100 rt 90 fd 100 rt 90 fd 100 rt 90 |
| Table 4.1 | |
By now you have to be good at drawing a square box. Table 4.1 shows a pseudocode description of what to do and its comparable list of Logo instructions.
Here are a few problems I have with the list of instructions.
What we need is the ability to give a name (also known as an identifier) to the list of instructions that draw this square box. And, we need to make the Logo interpreter aware of this name and treat it just like the built-in (aka primitive) commands it already has.
We can do this. The Logo interpreter in TG allows you to create your own commands. Here's how to define a new command that draws a box, 100 steps on a side.
to box100
fd 100 rt 90
fd 100 rt 90
fd 100 rt 90
fd 100 rt 90
end
Here is the TG applet; click the mouse in the CommandCenter and type it in for yourself.
| TG Applet |
You could have typed in:
to box100
fd 100 rt 90 fd 100 rt 90 fd 100 rt 90 fd 100 rt 90
end
I prefer the first example because it is easier to read and with the
symmetry of the four lines, it's easy to see that it is correct.
So, what have we done here?
We've used a special command ("to") to teach the Logo interpreter a new command. Just as it understands "forward" and "right" commands, it now understands what a "box100" command is and does. Since you defined it, it's called a user-defined procedure.
Try out your new command. In the CommandCenter, type "box100[Enter]" and see what this does. Did it do what you expected?
If not, here are a couple of diagnostic tools that will help you find out why not.
| TG Directive | Description | Example |
|---|---|---|
|
printprocs pp |
Print a list of user-defined procedures the Logo interpreter in TG knows. |
? printprocs box100 ? |
|
printtext pt |
Print the text (the source code representation) of a specified user-defined procedure. |
? printtext box100 to box100 fd 100 rt 90 fd 100 rt 90 fd 100 rt 90 fd 100 rt 90 end ? |
| Table 4.2 | ||
Even if your box100 procedure did work, you might want to check these handy tools out. Click the mouse in the CommandCenter and enter the command:
printprocs
The name of your new procedure, box100, should be printed.
If it is, next try:
printtext box100
"printtext box100" asks the Logo interpreter in TG to print out the
definition of the box100 command. Check it out to see
what's wrong. Reenter the definition until it matches what one
of the above examples looks like and works as you would expect.
Once it's working, it's time to reflect on what we have just done. The Logo interpreter now knows what to do when it gets a box100 command. We introduced this command with a TO command.
What does this special "TO" command look like?
The general form, the syntax, of a "TO" command, a procedure definition is:
| to | <name> |
| <Logo-Instructions> | |
| ... | |
| end | |
| Figure 4.1 | |
Procedure definitions consist of:
Once you've defined box100 you can use it like any of the Logo commands you've been using. You've just taught the Logo interpreter how to do something; you've added a new command to Logo's vocabulary.
Either go back up to the TG applet or use a small popup TG applet to play around with your new command. Try using it to draw a couple of boxes at different locations in TurtleSpace.
Then, try typing:
box100 rt 45a few times... What does this do? cool, huh?
Define another command that draws a different size of box, or a triangle, or something else... Have fun! Explore!
Did you notice that defining your own command (a procedure) causes the TurtleGraphics applet to behave differently?
Until now, every time you typed instructions into the CommandCenter, when you pressed the [Enter] key, something happened. When you typed "fd 100 rt 90" into the CommandCenter, the turtle would move forward in the graphics area of the applet and then rotate 90 degrees to the right. But, when you typed in your new command, which included four sets of "fd 100 rt 90" instruction lists, nothing happened in TurtleSpace. Is was as if you were typing into TG's editor, not into the CommandCenter.
Go back up to the TG applet and define some new command, something as simple as fwd100 that does nothing more than a single "fd 100" instruction. What you type is not important - just watch what happens as you type in the definition.
Did you notice that when you typed in: "to fwd100" and pressed [Enter], the CommandCenter name stripe changed:
"Defining Procedure: fwd100"appeared to the left of the word "CommandCenter" and the cursor moved to the next line, positioned to the right of a different prompt. The characters "> " are to the left of the cursor instead of the "? " characters you've been seeing. Also, the cursor was automatically indented a couple of spaces.
When you continued typing, adding an instruction line, e.g., "fd 100" and then pressed the "Enter" key, again, nothing happened in the graphics area. But, the "> " characters remained the prompt and the cursor was still indented a couple of spaces on the next new line.
Although it is not obvious, the Logo interpreter is doing something - it's checking to make sure that what you are typing is valid Logo. If it is, what you are typing is simply collected, stored away someplace, remembered for later use. You are teaching the interpreter how to do something new and it is remembering what you are typing.
Finally, when you typed in "end" and pressed the "Enter" key, (called the end line) the phrase "Defining&nbps;Procedure:&nbps;fwd100" disappeared in the CommandCenter's name stripe, the prompt characters went back to "? " and the cursor was no longer indented a couple of spaces.
You had completed the definition of the new command. All of the stuff that changed - the CommandCenter's name stripe, the prompt, the extra indentation - were feedback to you from the interpreter, to make sure you knew that you were in the process of defining a new command.
Once you had completed defining your new command, you could then use it. The Logo interpreter now recognizes it and does what you told it to do when you defined it. Typing its name to get the interpreter to perform it technically called invoking it, a.k.a. doing it, executing it, calling it, ...
In summary,
- defining a procedure is teaching the interpreter how to do something, identified by a supplied name;
- invoking a procedure (typing the procedure's
name into the CommandCenter) is telling the interpreter to do what
you taught it.
Logo Animation - Watching Procedure Invocation
Here is a small program consisting of two procedure
definitions (main and box100) and one invocation of
main and four invocations of box100. Watch how
the program is executed, step by step.
Using Procedural Abstraction to Rewrite DrawHouse
Now that you know how to define Logo procedures, I'm going to show you
how you can use them to write programs more easily. The programs
you write will be much easier to read and to extend. Let's take the
instructions that you wrote which draw a house and turn them into a bunch
of procedures.
I'll use TG's editor. If you have access to the TG application, start it up now, otherwise click here to get a small popup TG applet. Use one of these to follow along.
Use the Window->Editor->Open menu option to get TG's editor. Drag the CommandCenter and Editor name stripes to grow the editor subwindow to a reasonable size. To get maximum space for the editor, you can temporarily close the GraphicsCanvas with the Window->Canvas->Close menu option.
In the introduction, I mentioned the term "stepwise refinement" and this is how we will approach the task of writing a new DrawHouse program. What this means is that we will breakdown the writing into a series of steps. With each step, we will get closer to a complete program that does what we want - we refine our program.
All right, so the first thing I want to do is write a procedure which draws a house. I go to the editor window, click the left mouse button in it, and type:
to drawHousefollowed by [Enter]. The editor responds by opening a blank line and adding an end line following it. The cursor is placed on the middle, blank, line and is indented; it's ready for instructions which are to be part of my drawHouse procedure.
So what now? Where do we go from here? How can we break
down drawing the house into multiple, simpler tasks? What are the
objects that make up our house? That's it! I'll break the
procedure of drawing the house into subprocedures, one for each object.
My drawHouse procedure will invoke four other new
procedures:
So, I add invocations to these procedures to my drawHouse
procedure in the editor. I make it look like this:
to drawHouse
drawFront
drawRoof
drawDoor
drawWindow
end
This is a great first step. What I just did was to think about
what I needed, planned how to break the problem up into a set of simpler
problems, and then I wrote source code that should work.
But, this is only legal Logo source code IF I continue on and define all of the drawXxxx procedures I've invoked. Figure 4.2. shows what happens if I request that the interpreter perform the program as it stands (via Interpret->Editor Contents menu item). An error message pops up.
|
| Figure 4.2 |
What I could do is turn the lines of source code that are not yet ready into comments. When the interpreter sees the semicolon (";") indicating the start of a comment, it simply collects it and all the remaining characters on the line. This text is assumed to be a note for the programmer and anyone else reading the source code.
So, for now, I'll change the procedure to:
to drawHouse
;drawFront
;drawRoof
;drawDoor
;drawWindow
end
And now it's time to write each of the needed procedures. I
position the cursor on the "to drawHouse" line, hold down the
[Ctrl] key and press the "o" key. This opens an empty
line. I am now ready to add drawFront.
As I did for drawHouse, I type in:
to drawFrontfollowed by [Enter]. The editor responds by opening a blank line, followed by an end line, and positions the cursor, ready for me to type in the body. I add commands which get the turtle to draw the front of a house (same code as box100). Once I've done this, I can go back to the
drawHouse procedure
and remove the semicolon from the drawFront invocation.
I can now test what I've done so far. I drag the name stripes on the CommandCenter and the Editor to minimize their height, click the mouse in the CommandCenter and type "drawHouse" which invokes this procedure. The front of the house, a box, gets drawn.
By repeating this process with the remaining procedures I refine my program until it is completed.
Were you following along? If not, go do what I did now.
Did you get a house? If not check your source code and fix the mistakes.
Once you get a house, here's an exercise for your: instruct the turtle
to pick up the pen, move to another spot, put the pen down. Now
try invoking your DrawHouse procedure again. Did you get a
second house? If so, does it look like the first? If not,
figure out why.
The Procedure: main
There is one more procedure that you should write. Its name is
main. Why? What should it do?
What a procedure named main should do is initialize
the GraphicsCanvas (aka TurtleSpace). One possibility is to make
sure everything is like it is when you first see the TG applet.
This state is the same as when you startup the TG application.
This state can be described as:
Another possibility is to set some of the attributes to a state that you want for your program, e.g., maybe you want the pen size to start out at a width of 6 turtle-steps or the pen color to be blue.
Writing programs in TG is highly interactive. As you try things
out, write procedures and invoke them, you modify TG's state.
You move the turtle around; you change its heading; you change
the color of its pen; etc... Testing parts of your program as
you go is great; this helps you write a correct program fast.
But, a well written program has a known starting point and state and
this is main. It should contain all of the
commands needed to intialize TG's state properly.
Here's an example main procedure for use with
drawHouse.
to main
home clean setheading 0
pendown setpencolor 0 setpensize 2
drawHouse
end
Finally, what's with the name: main? I've
chosen this name because it is also the required
starting procedure for programs written in the programming languages:
C, C++, and Java.
main
|
|
|
|
|
drawGUY
/ | \
/ | \
/ | \
/ | \
/ | \
drawG drawU drawY
|
|
Think about how we've broken large programming problems into steps of manageable size. You've learned how to package them into procedures that have names which describe what the procedure does. This is called procedural abstraction.
This has been your first exposure to the use of abstraction - the most powerful concept you will use as a programmer.
A Piece of History: In the mid seventies, I stopped in to see a new friend in his office. He had stepped out for a bit, so I decided to wait. I checked out his bookshelf and grabbed a small, plain-black book that had the title "Structured Programming" written by O.J. Dahl, E.W. Dijkstra, and C.A.R. Hoare. A quick glance at its Table of Contents and the title of one chapter written by E.W. Dijkstra caught my eye - "On Our Inability To Do Much." I scanned the chapter and knew this book was monumental; I had to buy a copy and read it. This book is still in print, more than thirty years after it was published. What this lesson has attempted to introduce to you was at the heart of the matter in Dijkstra's contributions to this wonderful book.