BFOIT - Introduction to Computer Programming

Defining Operators

Introduction

In the previous lesson you learned about operators - procedures that produce an output.  Most of the operators you used needed an input or inputs and used it/them in some way to produce the output.

In this lesson, you will learn

  • how to output a value from a procedure that you write; you can write your own operators, another mechanism for creating more powerful procedural abstractions, and
  • about symbolic constants (names for values that never change) and how a procedure that outputs a value can serve this purpose.

Symbolic Constants

As I described in the very first lesson, a computer only works with binary numbers, called bits.  But I went on to show how things that you want to work with (e.g., characters, decimal numbers) are built out of these bits.  Systems programmers (wizards that write very low-level programs like assemblers, interpreters, compilers, system libraries, etc...) have been providing these niceties for almost ever.  This allows you to write programs that you can read, that make sense to you.

The programs you are now writing are starting to get big and they are taking hours to write.  In a few lessons, you will be writing programs that take many hours to complete.  This means that the programming will be spread over days.  Each time you return to continue programming, you need to pick back up where you left off.  The point is that your programs need to be easy for you to read so that you can refresh your mind on what's done and what's left to do.  I suggest that you get into the habit of giving names to many of the numbers that are starting to become ubiquitous in your programs.

The most obvious case is the numbers for the colors that you provide as an input to the setpencolor command.  I have trouble remembering many of the color numbers.  So, what I have done is to write little procedures that output numbers corresponding to the color the procedure's identifier represents.

This binding of a number to a meaningful identifier is what's called a symbolic constant in programming terms.

Here are a few examples:

   to blue 
     output 1
     end 
   to forest 
     output 10   
     end 
   to north 
     output 0
     end 
   to east 
     output 90
     end 

Once defined, the first two of these procedures can be used to provide an input to setpencolor.  The colors above are some of the colors I used in my seascape program.  Here is what part of my main procedure contained - the part that used the color procedures.

   to main 
     hideturtle
     setpencolor blue
     waves -240 20
     waves -130 -15
     setpencolor forest   
     fish 100 -100
     fish 50 -150
     ...
     end

Defining the procedures blue and forest and then using them in appropriate places makes the program much easier to read.  Trust me, this is good...

The symbolic constants for headings for the turtle can be used similarly.  north, east, etc... procedures can be used as inputs to the setheading command.  Get into the habit of starting all of your programs with symbolic constants - part of the vocabulary for the story you are about to write.  Yes, think of the process of writing a program as similar to writing anything that is descriptive.

Creating Your Own Operators

The key to the above procedure definitions is the command, output.  When a Logo interpreter performs an output command, it exits the procedure containing it, producing whatever output's input is - as the procedure's output.  This can be confusing at first.  output is not an operator.  Figure 9.1 shows what happens if you try to put it where an operator is required.

Figure 9.1

Ok, let's move on and write some more interesting procedures which are operators.

In the previous lesson there were examples of arithmetic expressions that computed and displayed the circumference and the area of a circle.  Here is one of the procedures.

   to printCircleCircum :radius 
     println product 2 (product 3.14159 :radius)   
     end 

It would be more useful to separate the println functionality from the computation piece.  It's simple...  Just replace println with output.

   to circleCircumference :radius 
     output product 2 (product 3.14159 :radius)   
     end 

Notice that I changed the name of the procedure.  You always want the name of the procedure to reflect what it does.  This is how we achieve our abstraction.

This new procedure, now an operator, is simple to use with the println command, e.g., here is an example of its use in the CommandCenter.

   ? println circleCircumference 4   
   25.13272
   ? 

Use the following TG applet to try it out for yourself.  Type the circleCircumference definition into the Editor, then invoke it a few times in the CommandCenter to see what you get.

Your browser does not support Java applets. Click here for more information. TG Programming Environment Applet

Practice: A Couple More Operators For You To Write

You really don't learn about something deeply by reading about it or hearing someone lecture about it - you learn by trying to do it.  Here are a few exercises that you can play with.

  1. In the previous lesson you wrote printCircleArea.  Write a procedure named circleArea with an input for the radius of the circle.  It should produce an output that is the area of the circle.  Test it with the inputs in Table 9.1 and make sure you get good answers.

    Input
    Area
    2
    12.56636
    3
    28.27431
    5
    78.53975
    11
    380.13239
    Table 9.1
  2. Part of the arithmetic expression you wrote to compute the area of a circle involved squaring a number.  Squaring a number is a common thing to do.  Write a procedure named square with an input :number that produces number-squared.  There is also a constant value in the expression, PI, which should be defined as a symbolic constant - define the procedure PI which outputs 3.14159.  Redefine your circleArea using these new procedures.  Once again, test it...


After you've completed writng these procedures, check what you came up with here.

Practice: Random In Range

OK, now for an operator that would have been nice to have in one of the programs we wrote in the last lesson, RandomBoxes.  To refresh your memory, we painted a bunch of solid, colored boxes at random locations on the graphics canvas.  We needed a couple of random numbers in ranges other than the standard 0...x-1 provided by the random x operator.  We needed random numbers in a range determined by the height and width of the graphics canvas.  As an example, if the canvaswidth operator outputs 600, we needed random numbers in the range of -300 ... 300-boxWidth.

I want a new operator, let's call it randomInRange, which produces an output, a number that's in any range of numbers I provide as inputs.  As an example, for the invocation "randomInRange -50 50" I want an output that is random and is greater-than or equal-to -50 AND is less-than or equal-to 50.  Figure 9.2 demonstrates a working version producing random numbers in the range of -2 through 2.

Figure 9.2

Ok, either go off and write it or if you need a little help to get started, here's a skeleton.

   to randomInRange :min :max 
     ; output <random number gtr-or-eql :min and less-or-eql :max>   
     end 

At this point, don't hesitate to play around in the TG applet to figure out what you need to do.  Follow the steps we've been using to write all of our programs.

  1. Understanding the Problem (figure out what you know, and what you don't know)
  2. Devising a Plan (write a pseudo code description of what to do, draw a plumbing diagram or diagrams to visualize what you will do)
  3. Carrying out the Plan - type in your source code and test it; does it work?  If not, verify your code matches what you developed in step 2 and review what you came up with in steps 1 and 2.

Don't read further until you've at least made some attempt at writing the randomInRange procedure.

First hint...

If you think about it, your output needs to be at least what the input :min is given when randomInRange is invoked.  Since the smallest value returned by random is 0, you are going to need to add :min to the output from random.  So, our pseudocode now looks like:

   ;output a random number that is
   ;greater than or equal to :min and
   ;less than or equal to :max 
   to randomInRange :min :max 
     ; output sum :min random <magnitude of range of numbers>   
     end 

Don't read further until you've made an attempt to complete this procedure and experimented with it

Second hint...

At this point, I suggest that you write an operator named magnitude.  Here's the expanded pseudo code:

   ;output the absolute value of 
   ;the difference of two inputs 
   to magnitude :min :max 
     ; output <magnitude of range of numbers>
     end 

   ;output a random number that is 
   ;greater than or equal to :min and 
   ;less than or equal to :max 
   to randomInRange :min :max 
     output sum :min (random magnitude :min :max)   
     end 

OK... finish the procedure.

Use repeat to test your code.  If it doesn't work, help is on the way - read the next section.  If your code works, congratulations!  Now read the next section because it's a really cool feature you are sure to use in the future.

TRACE - A Debugging Tool

    "Failure is an integral part of success," Mead says. "You
    learn from every one of your failures.  I used to tell
    students, 'You've got to listen to the silicon. It's trying
    to tell you something.'"

    If you build something or do something and it doesn't work
    out, he says you can curse and swear at it. Or, you can learn
    from it.

    "The physical world is perfectly willing to share with you
    how it works. If you listen. But, if you have your mind made
    up, you can go for years and not hear it," Mead says.

                     from an article in Investors Business Daily
                     May 13, 2003 

Time for you to learn more about tracing in TG.  It can help you understand what's happening when your program is being executed and not doing what you expect.  Remember, trace is a directive you enter into the CommandCenter.  it's similar to a command, but can't be placed into the body of a procedure.  It directs TG to print out very useful stuff as your program is executed.

So, once you have your magnitude and randomInRange procedures entered into the Editor, switch keyboard focus to the CommandCenter and type:

   ? trace magnitude
   ? println randomInRange -4 4    

Since TG has been directed to trace the procedure magnitude, what you should see is somthing like:

   ? println randomInRange -4 4
   Entering magnitude -4 4
   Exiting magnitude, output: 8   
   -3
   ? 

TG let you know that magnitude was invoked with the inputs: -4 and 4.  It's body was executed and its output was 8.

Well, with this information, you can see that there is a problem with the code I gave you.  There's a bug in it.  randomInRange -4 4 will never output a 4 (the value provided for the :max input in our trial).

random 8 outputs a number in the range 0 - 7.  To get an output of 4 we need to give random an input equal to the output of magnitude plus 1.

Fix the bug (if you hadn't discovered it and fixed it on your own)...

Finally, modify your program from the last lesson that draws boxes at random locations so that it uses randomInRange.  Also add symbolic constants where appropriate...  Use them to make your program more readable.

You may want to use an operator that TG's interpreter understands that we have not talked about yet.  You may not need it, but I used it.  Table 9.1 gives you the scoop on minus which simply takes a number and negates it, just like multiplying the number by -1.

Command Inputs Description
MINUS number Outputs the negative of number
Table 9.1

Project: A Grid Toolkit

The reason that I have had you drawing axes and grids in many of the previous lessons is that they are very common in programs.  Here are some windows which I captured; they are, top-to-bottom, left-to-right:

Each of these programs contains at least one graphical object that is grid-like.  I am going to take our use of abstraction to another level.  We are going to write a bunch of procedures that provide stuff we will need in many programs.  We are going to get started creating a GridToolkit

A Grid Toolkit Contract

The first thing I'm going to do is write the rules for using the GridToolkit.  These rules are going to be its contract.  We will follow the rules when we write the procedures in the toolkit.  And... we will follow the rules when we use the procedures in the toolkit in our programs.  I talked a bit about contracts, as they applied to a single procedure, when I introduced defining you own procedures.  Our first GridToolkit rules will be

  1. Definition: our grid is a bunch of squares, all of equal size, arranged in rows, each with an equal number of columns
  2. Definition: the squares making up our grid will be called cells
  3. All procedures in the toolkit will have names starting with "grid"
  4. A cell can be identified in one of two ways:
    • by its row and column numbers. Rows are ordered from top to bottom, with the top row numbered zero. Columns are ordered left to right with the leftmost column numbered zero.
    • by its index. the index (number) of the top-left cell is zero and then indicies increase left-to-right and then top-to-bottom.

The Grid Toolkit Source Code

Given these rules, here are the procedures that make up our first pass at a GridToolkit.

; GridToolkit_9 - Library of Procedures For Drawing a Grid
; -------------

; Symbolic Constants
; -------- ---------

; SETPENCOLOR values
 to black 
   output 0
   end 
 to white 
   output 7
   end 
  
 ; color for a cell's background 
 to gridCellColor 
   output white
   end 
  
 ; color of grid's frame 
 to gridFrameColor 
   output black
   end 

 ; size of the sides of a grid cell 
 to gridCellSize 
   output 40
   end 

 ; one-half the size of a side of a grid cell 
 to gridHafCellSiz 
   output quotient gridCellSize 2
   end 

 ; number of columns in the grid 
 to gridNumCol 
   output 8
   end 

 ; number of rows in the grid 
 to gridNumRow 
   output 5
   end 

 ; number of cells in the grid 
 to gridNumCells 
   output product gridNumCol gridNumRow
   end 

 ; height of grid in turtle steps 
 to gridHeight 
   output product gridNumRow gridCellSize
   end 

 ; width of grid in turtle steps 
 to gridWidth 
   output product gridNumCol gridCellSize
   end 

 ; X coordinate for left side of the grid 
 to gridLeftX 
   output minus product gridCellSize (quotient gridNumCol 2)
   end 

 ; X coordinate for right side of the grid 
 to gridRightX 
   output difference (sum gridLeftX gridWidth) 1
   end 

 ; Y coordinate for bottom of the grid 
 to gridBottomY 
   output minus product gridCellSize (quotient gridNumRow 2)
   end 

 ; Y coordinate for top of the grid 
 to gridTopY 
   output difference (sum gridBottomY gridHeight) 1
   end 


; Grid Procedures
; ---- ----------

 ; move turtle to top-left corner of grid 
 to gridGotoTopLeft 
   penup
   setxy gridLeftX gridTopY
   end 

 ; move the turtle to the bottom-left corner of the cell
 ; specified by the index :idx
 ; indices start at zero for the top-left cell and increase
 ; left-to-right, top-to-bottom 
 to gridGotoCell :idx 
   gridGotoTopLeft
   setheading 180
   forward product gridCellSize (int quotient :idx gridNumCol)
   forward gridCellSize
   setheading 90
   forward product gridCellSize (remainder :idx gridNumCol)
   end 

 ; move the turtle to the center of cell specified by the index :idx
 ; indices start at zero for the top-left cell and increase
 ; left-to-right, top-to-bottom 
 to gridGotoCellCtr :idx 
   gridGotoCell :idx
   setheading 90 forward gridHafCellSiz
   left 90 forward gridHafCellSiz
   end 

 ; draw/redraw the cell specified by the index :idx
 ; indices start at zero for the top-left cell and increase
 ; left-to-right, top-to-bottom
 ; the :color input specifies the cell's background color
 ;the :color input specifies the cell's new background color 
 to gridCellFill :idx :color 
   gridGotoCellCtr :idx
   setpensize gridCellSize setpencolor :color
   setheading 0 pendown
   forward gridHafCellSiz back gridCellSize forward gridHafCellSiz
   penup forward gridHafCellSiz left 90 forward gridHafCellSiz
   setpensize 1 setpencolor gridFrameColor
   setheading 90 pendown
   repeat 4 [forward gridCellSize right 90]
   end 

 ; draw the grid 
 to gridPaint 
   gridGotoTopLeft
   setheading 180 forward quotient gridHeight 2 setheading 90
   setpensize gridHeight setpencolor gridCellColor
   pendown forward gridWidth
   gridGotoTopLeft
   setpensize 1 setpencolor gridFrameColor
   setheading 180 pendown
   repeat gridNumRow [fd gridCellSize lt 90 fd gridWidth bk gridWidth rt 90]
   gridGotoTopLeft
   setheading 90 pendown
   repeat gridNumCol [fd gridCellSize rt 90 fd gridHeight bk gridHeight lt 90]
   end 

Checkout the Grid Toolkit

If you have the TG programming environment on your computer, copy & paste the GridToolkit source code into TG's Editor so that you can play with it.  If you want to use the TG applet on this web page to play, use the "loadcode GridToolkit_9.jlogo" directive in the CommandCenter to get the source code in the Editor.

It has initial default values in place in all of the procedures acting as symbolic constants.  These will be changed as necessary in programs that you write that includes the GridToolkit.  But, with the default values in place, you can display a grid by invoking a single procedure: gridPaint.  Then you can paint individual cells by invoking gridCellFill with two inputs, the cell's index and the color number it is to be filled with.  Add the following code after the GridToolkit stuff in the Editor or type it into the CommandCenter.

    gridPaint
    gridCellFill 0 1
    gridCellFill (difference gridNumCells 1) 4   

Then try filling random cells with random colors; here's an instruction that does this.

    repeat gridNumCells [gridCellFill random gridNumCells random 32]   

Play with the code... change some symbolic constants like the size of the cells, the number of rows and or columns...

Why the Gift?

So, why did I give you all of the source code for the GridToolkit without making you work for it?

Answer: you will learn a lot by reading someone else's source code.

Long ago, when I was convinced that Logo was a much better language to to use to introduce programming concepts than Java, the first thing I did was to read a lot of Logo source code.  Fortunately for me, there is a lot of it available.  Brian Harvey's Computer Science, Logo Style books, available free on-line, are a great introduction.  See the ItP acknowledgements page for additional sources of great Logo programs.

Now it's your turn to read some code.  Read through the GridToolkit source code.  Use it to complete the following project.

Draw Balls in the Cells

Write a procedure that draws a colored ball in the center of a specified cell.  Then use it and procedures in the GridToolkit to write a program which draws random colored balls in random cells.  Here is an example of what my program painted in the graphics canvas.

I used a repeat command to paint 20 randomly colored balls into 20 random cells.

repeat 20 [ gridGotoCellCtr random gridNumCells drawBall ballSize random 16 ]
			
			

Stamp Turtle Shapes in the Cells

Write a procedure that sets the shape of the turtle to a random one.  Read about the stamp command using the help directive in the CommandCenter and then use it to paint shapes in random cells.  Here is an example of what my program painted in the graphics canvas.

Two New Built-in Operators - Why?

In the GridToolkit, I used two built-in operators that you have not seen before.  What are they?  Why did I need to use them?


After you've thought about it for a while and have some sort of answers, check what you came up with here.

Why Count Starting With Zero?

In GridToolkit, rows and columns were numbered starting with zero instead of one.  The index that is used to identify a particular cell also started with the first cell numbered zero.  This convention has been in place for decades in the world of computer programming.

The reason is that this simplifies the code.  Can you find the code in our GridWorld which is simpler than it would have to be if we started numbering the index at one instead of zero?  How would the code need to be changed if the base was one instead of zero?


After you've thought about it for a while and have some sort of an answer, check what you came up with here.

Summary

We've made another quantum jump in how we can reduce the complexity of the programs we write.  You now know how to define your own operators, procedures which output values.  Symbolic constants, a very simple form of a user-defined operator, can make your programs much more readable.  And, binding meaningful names to complex expressions, is just one more use of abstraction, an approach to writing programs that are much more easily understood.  And, the side benefit is that a program that is easy to read, that's easily understood. is that it will probably do what you want, what you intended it to do.


Back to Operators & Expressions
Go to the Table of Contents
On to Words & Sentences

Public Domain Mark
This work (BFOIT: Introduction to Computer Programming, by Guy M. Haas),
identified by Berkeley Foundation for Opportunities in IT (BFOIT),
is free of known copyright restrictions.