In this chapter:
– Declaring and defining a function
– Calling a function
– Arguments and parameters
– Returning a value
– Reusability
The examples provided in Chapter 1 through Chapter 6 are short. I probably have not shown a sketch with more than 100 lines of code. These programs are the equivalent of writing the opening paragraph of this chapter, as opposed to the whole chapter itself.
Processing is great because you can make interesting visual sketches with small amounts of code. But as you move forward to looking at more complex projects, such as network applications or image processing programs, you will start to have hundreds of lines of code. You will be writing essays, not paragraphs. And these large amounts of code can prove to be unwieldy inside of your two main blocks — setup() and draw().
Functions are a means of taking the parts of a program and separating them out into modular pieces, making code easier to read, as well as to revise. Let’s consider the video game Space Invaders. The steps for draw() might look something like:
• Draw spaceship.
• Draw enemies.
• Move spaceship according to user keyboard interaction.
• Move enemies.
Before this chapter on functions, I would have translated the above pseudocode into actual code, and placed it inside draw(). Functions, however, will let you approach the problem as follows:
The above demonstrates how functions make life easier with clear and easy to manage code. Nevertheless, I am missing an important piece: the function definitions. Calling a function is old hat. You do this all the time when you write line(), rect(), fill(), and so on. Defining a new “made-up” function is going to require some more work on your part.
Before I launch into the details, let’s reflect on why writing your own functions is so important:
• Modularity — Functions break down a larger program into smaller parts, making code more manageable and readable. Once I have figured out how to draw a spaceship, for example, I can take that chunk of spaceship drawing code, store it away in a function, and call upon that function whenever necessary (without having to worry about the details of the operation itself).
• Reusability — Functions allow you to reuse code without having to retype it. What if I want to make a two player Space Invaders game with two spaceships? I can reuse the drawSpaceShip() function by calling it multiple times without having to repeat code over and over.
In this chapter, I will look at some of my previous sketches, written without functions, and demonstrate the power of modularity and reusability by incorporating functions. In addition, I will further emphasize the distinctions between local and global variables, as functions are independent blocks of code that will require the use of local variables. Finally, I will continue to follow Zoog’s story with functions.
In Processing, you have been using functions all along. When you say line(0, 0, 200, 200); you are calling the function line(), a built-in function of the Processing environment. The ability to draw a line by calling the function line() does not magically exist. Someone, somewhere, defined (i.e., wrote the underlying code for) how Processing should display a line. One of Processing’s strengths is its library of available functions, which you have started to explore throughout the first six chapters of this book. Now it’s time to move beyond the built-in functions of Processing and write your own user-defined (a.k.a. “made-up”) functions.
A function definition (sometimes referred to as a “declaration”) has three parts:
• Function name.
• Arguments.
returnType functionName(parameters) {
// Code body of function
For now, let’s focus solely on the function name and code body, ignoring return type and parameters.
This is a simple function that performs one basic task: drawing an ellipse colored black at coordinate (50,50). Its name — drawBlackCircle() — is arbitrary (I made it up) and its code body consists of two instructions (you can have as much or as little code as you choose). It’s also important to remind ourselves that this is only the definition of the function. The code will never happen unless the function is actually called from a part of the program that is being executed. This is accomplished by referencing the function name, that is, calling a function, as shown in Example 7-2.
Let’s examine the bouncing ball example from Chapter 5 and rewrite it using functions, illustrating one technique for breaking a program down into modular parts. Example 5-6 is reprinted here for your convenience.
Once I have determined how I want to divide the code up into functions, I can take the pieces out of draw() and insert them into function definitions, calling those functions inside draw(). Functions typically are written below draw().
Note how simple draw() has become. The code is reduced to function calls; the detail for how variables change and shapes are displayed is left for the function definitions. One of the main benefits here is the programmer’s sanity. If you wrote this program right before leaving on a two-week vacation in the Caribbean, upon returning with a nice tan, you would be greeted by well-organized, readable code. To change how the ball is rendered, you only need to make edits to the display() function, without having to search through long passages of code or worrying about the rest of the program. For example, try replacing display() with the following:
Another benefit of using functions is greater ease in debugging. Suppose, for a moment, that the bouncing ball function was not behaving appropriately. In order to find the problem, I now have the option of turning on and off parts of the program. For example, I might simply run the program with display() only, by commenting out move() and bounce():
The function definitions for move() and bounce() still exist, only now the functions are not being called. By adding function calls one by one and executing the sketch each time, I can more easily find the location of the problematic code.
Just a few pages ago I said “Let’s ignore ReturnType and Arguments.” I did this in order to ease into functions by sticking with the basics. However, functions possess greater powers than simply breaking a program into parts. One of the keys to unlocking these powers are the concepts of arguments and parameters.
Arguments are values that are “passed” into a function. You can think of them as inputs that a function needs to do its job. A function that causes a creature to move a certain number of steps needs to know how many steps you want the creature to move. Instead of merely saying “move,” you might say, “move ten steps,” where “ten” is the argument.
When you define such a “move” function, you are required to give each argument a name. That way, the function can refer to the arguments it receives by the particular name that you specify. To illustrate, let’s rewrite drawBlackCircle() to include a parameter:
A parameter is simply a variable declaration inside the parentheses in the function definition. This variable is a local variable (remember the discussion in Section 6-5 on page 104?) to be used in that function (and only in that function). The black circle will be sized according to the value of diameter, which will automatically be assigned the value that you pass the function when you call it. For example, when you say drawBlackCircle(100), the value 100 is the argument. That 100 gets assigned to the diameter parameter, and the function itself uses diameter to draw the circle. When you call drawBlackCircle(80), the argument 80 is assigned to parameter diameter, and the function body then uses diameter to draw the circle.
drawBlackCircle(16); // Draw the circle with a diameter of 16
drawBlackCircle(32); // Draw the circle with a diameter of 32
You could also pass another variable or the result of a mathematical expression (such as mouseX divided by 10) into the function. For example:
drawBlackCircle(mouseX / 10);
This, by the way, is exactly what you did in Chapter 1 when you first started drawing in Processing. To draw a line, for example, you couldn’t just say draw a line. Rather, you had to say draw a line from some (x,y) to some other (x,y). You needed four arguments.
line(10, 25, 100, 75); // Draw a line from (10,25) to (100,75).
The key difference here is that you didn’t write the line() function! The creators of Processing did, and if you delve into the Processing source itself, you’ll find a function definition with four parameters.
void line(float x1, float y1, float x2, float y2) {
// This functions requires four parameters
// which define the end points (x1,y1) and (x2,y2)
// of a line!
}
Parameters pave the way for more flexible, and therefore reusable, functions. To demonstrate this, let’s look at code for drawing a collection of shapes and examine how functions allow you to draw multiple versions of the pattern without retyping the same code over and over.
Leaving Zoog until a bit later, consider the following pattern resembling a car (viewed from above as shown in Figure 7-1):
To draw a second car, I’ll repeat the above code with different values, as shown in Figure 7-2.
It should be fairly apparent where this is going. After all, I am doing the same thing twice — why bother repeating all that code? To escape this repetition, I can move the code into a function that displays the car according to several parameters (position, size, and color).
In the draw() function, I then call the drawCar() function three times, passing four arguments each time. See the output in Figure 7-3.
Technically speaking, parameters are the variables that live inside the parentheses in the function definition, that is, void drawCar(int x, int y, int thesize, color c). Arguments are the values passed into the function when it is called, that is, drawCar(80, 175, 40, color(100, 0, 100)). The semantic difference between arguments and parameters is somewhat trivial and you should not be terribly concerned if you confuse the use of the two words from time to time.
The concept to focus on is this ability to pass arguments. You will not be able to advance your programming knowledge unless you are comfortable with this technique.
Let’s go with the word pass. Imagine a lovely, sunny day and you’re playing catch with a friend in the park. You have the ball. You (the main program) call the function (your friend) and pass the ball (the argument). Your friend (the function) now has the ball (the argument) and can use it however he or she pleases (the code itself inside the function). See Figure 7-4.
Exercise 7-6: Create a design for a flower. Can you write a function with parameters that vary the flower’s appearance (height, color, number of petals, etc.)? If you call that function multiple times with different arguments, can you create a garden with a variety of flowers?
There is a slight problem with the “playing catch” analogy. What I really should have said is the following. Before tossing the ball (the argument), you make a copy of it (a second ball), and pass it to the receiver (the function).
Whenever you pass a primitive value (int, float, char, etc.) to a function, you do not actually pass the value itself, but a copy of that variable. This may seem like a trivial distinction when passing a hard-coded number, but it’s not so trivial when passing a variable.
The following code has a function entitled randomizer() that receives one parameter (a floating point number) and adds a random number between −2 and 2 to it. Here is the pseudocode.
• num is displayed: 10
• A copy of num is passed into the parameter newnum in the function randomizer().
• In the function randomizer():
– a random number is added to newnum.
– newnum is displayed: 10.34232
• num is displayed again: Still 10! A copy was sent into newnum, so num has not changed.
void setup() {
float num = 10;
println("The number is: " + num);
randomizer(num);
println("The number is: " + num);
}
void randomizer(float newnum) {
newnum = newnum + random(−2, 2);
println("The new number is: " + newnum);
}
Even though the variable num was passed into the variable newnum, which then quickly changed values, the original value of the variable num was not affected because a copy was made.
I like to refer to this process as “pass by copy,” however, it’s more commonly referred to as “pass by value.” This holds true for all primitive data types (the only kinds I’ve covered so far: integer, float, etc.), but is not quite the same with objects, which you will learn about in the next chapter.
This example also provides a nice opportunity to review the flow of a program when using a function. Notice how the code is executed in the order that the lines are written, but when a function is called, the code leaves its current line, executes the lines inside of the function, and then comes back to where it left off. Here is a description of the preceding example’s flow:
2. Print the value of num.
3. Call the function randomizer.
a. Set newnum equal to newnum plus a random number.
b. Print the value of newnum.
4. Print the value of num.
So far you have seen how functions can separate a sketch into smaller parts, as well as incorporate arguments to make it reusable. However, there is one piece still missing from this discussion, and it is the answer to the question you have been wondering all along: “What does void mean?”
As a reminder, let’s examine the structure of a function definition again:
returnType functionName(parameters) {
// Code body of function
}
OK, now let’s look at one of the functions:
void drawCar(int x, int y, int theSize, color c) {
int offset = theSize/4;
// Draw main car body
rectMode(CENTER);
stroke(200);
fill(c);
// Draw four wheels relative to center
fill(200);
rect(x − offset, y − offset, offset, offset/2);
rect(x + offset, y − offset, offset, offset/2);
rect(x − offset, y + offset, offset, offset/2);
rect(x + offset, y + offset, offset, offset/2);
}
drawCar is the junction name, x is a parameter to the function, and void is the return type. All the functions I have defined so far did not have a return type; this is precisely what void means: no return type. But what is a return type and when might you need one?
Let’s recall for a moment the random() function examined in Chapter 4. I asked the function for a random number between 0 and some value, and random() graciously heeded my request and gave back a random value within the appropriate range. The random() function returned a value. What type of a value? A floating point number. In the case of random(), therefore, its return type is a float.
The return type is the data type that the function returns. In the case of random(), I did not specify the return type, however, the creators of Processing did, and it’s documented on the reference page for random().
Each time the random() function is called, it returns an unexpected value within the specified range. If one argument is passed to the function it will return a float between zero and the value of the argument. The function call random(5) returns values between 0 and 5. If two arguments are passed, it will return a float with a value between the arguments. The function call random(−5, 10.2) returns values between −5 and 10.2.
If you want to write your own function that returns a value, you have to specify the type in the function definition. Let’s create a trivially simple example:
Instead of writing void as the return type as I have in previous examples, I now write int. This specifies that the function must return a value of type integer. In order for a function to return a value, a return statement is required. A return statement looks like this:
return valueToReturn;
If you do not include a return statement, Processing will give you an error:
• This function must return a result of type int.
As soon as the return statement is executed, the program exits the function and sends the returned value back to the location in the code where the function was called. That value can be used in an assignment operation (to give another variable a value) or in any appropriate expression. See the illustration in Figure 7-5. Here are some examples:
int x = sum(5, 6, 8);
int y = sum(8, 9, 10) * 2;
int z = sum(x, y, 40);
line(100, 100, 110, sum(x, y, z));
I hate to bring up playing catch in the park again, but you can think of it as follows. You (the main program) throw a copy of a ball to your friend (a function). After your friend catches that ball, he or she thinks for a moment, puts a number inside the ball (the return value) and passes it back to you.
Functions that return values are traditionally used to perform complex calculations that may need to be performed multiple times throughout the course of the program. One example is calculating the distance between two points: (x1,y1) and (x2,y2). The distance between pixels is a very useful piece of information in interactive applications. Processing, in fact, has a built-in distance function that you can use. It’s called dist().
This line of code calculates the distance between the mouse location and the point (100, 100). For the moment, let’s pretend Processing did not include this function in its library. Without it, you would have to calculate the distance manually, using the Pythagorean Theorem, as shown in Figure 7-6.
float dx = mouseX − 100;
float dy = mouseY − 100;
float d = sqrt(dx*dx + dy*dy);
If you wanted to perform this calculation many times over the course of a program with many different pairs of coordinates, it would be easier to move it into a function that returns the value d.
Note the use of the return type float. Again, I do not have to write this function because Processing supplies it for me. But since I did, I can now show an example that makes use of this function.
Zoog is now ready for a fairly major overhaul.
• Reorganize Zoog with two functions: drawZoog() and jiggleZoog(). Just for variety, I am going to have Zoog jiggle (move randomly in both the x and y directions) instead of bouncing back and forth.
• Incorporate parameters so that Zoog’s jiggliness is determined by the mouseX position and Zoog’s eye color is determined by Zoog’s distance to the mouse.
Exercise 7-9: Write a function that draws Zoog based on a set of parameters. Some ideas are: Zoog’s x and y coordinates, its width and height, eye color.
Exercise 7-11: Rewrite your Lesson Two Project using functions.
3.128.199.162