I'm an Open University Student studying for a BSc Computing. I live in Lymm, just south of Warrington, in the North West of England with my wife Carole. I spend a lot of time wondering why the world can't be a nicer place: and then I get on with writing a program or producing a website.

Saturday, 30 May 2009

Chunk 14: Second 800 words....

So have two functions that can draw circles and squares, what if we what one function that is capable of drawing either? Well, this is simply a case of providing an argument that tells the function which type of figure to draw: again, the magic is in the function arguments.

Let us create a new function called ‘drawShape’ then, and have it accept and argument specifying the type of shape followed by the usual position and size arguments that we’ve been using above; we’ll end up with a function like this:

void drawShape( int shapeType, float x, float y, float width ) {
// The code to do the drawing
}

We’ll use ‘width’ as the generic size since it covers the diameter of the circle well also.

We come to a slight stumbling block here though – what do we pass as an argument for ‘shapeType’? The common method for answering this is to define some constant values that are agreed upon and defined globally at the top of the file, so we could have:

int doCIRCLE = 1;
int doSQUARE = 2;

and then our function will be:

void drawShape( int shapeType, float x, float y, float width ) {
if ( shapeType == doCIRCLE ) {
drawCircle(x,y,width);
}
if ( shapeType == doSQUARE ) {
drawSquareCentred(x,y,width);
}
}

If we then modify our setup routine as follows, we find that we get exactly the same result as before.

void setup(){
size(400,400);
background(255);
drawShape(doSQUARE,200,200,150);
drawShape(doCIRCLE,200,200,150);
}

If, at a later date you were to write a function to draw an octagon, then it would be very simple to add that as a further option to the drawShape function by defining a new constant value, eg doOCTAGON. This would then mean that everywhere that you had used drawShape would immediately be able to take advantage of the extra shape without a need to modify the code in each place where it was used.

This is a very powerful aspect of functions and the design of the argument list. It should be noted, however, that a single function should be limited to performing a specific task – if a function is designed to draw a shape, do not then expand it to also calculate some complex mathematical sum on a dataset also, that should be the job for a different function. Sometimes it is all too tempting to add in a bit of extra functionality because ‘it’s doing something else with the same data’, but this generally just leads to difficult to track down bugs and very hard to understand code.

A further useful aspect of functions is their ability to make calls to themselves! This is called ‘recursion’. It is a technique that should generally be avoided, but in controlled situations can be very useful. The reason for its avoidance is that is can very quickly use up a lot of processing memory if the number of recursions is not monitored in some way.

The example we are going to look at uses a ‘depth’ system to keep the recursion in check. This example will show a method of drawing a grid of circles specified by a depth argument… the depth indicates how many multiples of two are applied to the grid along each side, so a depth of 1 would give a 2 by 2 square, a depth of 2 would give a (2 x 2) by (2 x 2) square and so on. We’ll call this function ‘drawCircles’, and you will see when you enter the function the first check is to see if we need to go any deeper before starting to draw: if we do, then we split the area up into quartiles and make a further call for each of the four quartiles with depth reduced by one… this goes on until there’s no need to go any deeper and so a circle is drawn and the function returns either to the original called program or to the next place within the drawCircles function that it had got up to before.

void drawCircles(float x, float y, float diameter, int depth) {
if ( depth > 0 ) {
float halfDiameter = diameter / 2 ;
drawCircles(x - halfDiameter, y - halfDiameter, halfDiameter, depth - 1) ;
drawCircles(x + halfDiameter, y - halfDiameter, halfDiameter, depth - 1) ;
drawCircles(x - halfDiameter, y + halfDiameter, halfDiameter, depth - 1) ;
drawCircles(x + halfDiameter, y + halfDiameter, halfDiameter, depth - 1) ;
}
else {
drawCircle(x,y,diameter);
}
}

It can be seen from this function declaration that drawCircles is called exactly the same as drawCircle, but with the addition of a ‘depth’ argument.

A setup as follows produces the geometric pattern shown:

void setup(){
size(400,400);
background(255);
fill(229,138,19);
drawSquareCentred(200,200,150);
fill(229,19,19);
drawCircles(200,200,150,1);
fill(169,19,229);
drawCircles(200,200,150,2);
fill(56,19,229);
drawCircles(200,200,150,3);
fill(19,229,227);
drawCircles(200,200,150,4);
fill(46,229,19);
drawCircles(200,200,150,5);
}

Tuesday, 26 May 2009

Chunk 14: First 900 words....

{After lots of things getting in the way I'm pressing on quickly working from the outline I wrote back in January!}

Chunk 14: Functions 1

Up to this point all our coding has been linear: that is to say our sketches start at the top and work their way right through until they get to the bottom with everything spelt out in full.

There are times when we’ve almost repeated the same pieces of code multiple times within the same sketch. Wouldn’t it be great if there was a way that you could just write that bit once and then tell the computer to use it again and again? This is where ‘functions’ come in.

A function is a mini program that can be used over and over by a main program. Every function has a name and, optionally, has one or more arguments passed to it. The basic structure of a function is:

void function_name ( argument_list ) {
// function content
}

The ‘void’ indicates that the function does not return any value; the function name should follow the normal naming conventions that you would expect for any identifier; the round brackets enclose any arguments that the function will be expecting, these will be examined shortly; finally, the braces are used to mark the beginning and end of the code that comprises the function content, this can range from a simple statement of a single line to a complex routine of many lines.

The round brackets and braces must always be present even if they have nothing to enclose: that is to say, if there are no arguments, the round brackets are still present but with nothing between them; and if there is no code (if the function is a placeholder to be filled in later for example) then the braces are still present. In the latter case it is usual to insert a comment as a reminder about the empty function!

The argument list is where all the work is done: this is where all the differences are made for a function. Rather than always doing the same thing, using arguments allows us to influence the way that the function will operate. Arguments are lists in a similar fashion to the way that variables are declared except that they are separated by commas: so you would declare a variable type followed by a variable name, and then a comma followed by the same sequence repeated for however many arguments are required. If you ever find yourself writing a function with more than five or six arguments though, then your function is most likely trying to do too much and should be examined to see if it can be broken down into two or more simpler functions because the more complicated a function is, the harder it is to debug and the easier it is for an error to split through unnoticed.

Let us look at an example: we can specialise the standard ‘ellipse’ routine to make our own function to draw a circle, which we shall call ‘drawCircle’, to save having to repeat the third argument every time we want a circle and avoid the possibility of mistyping the repeat.

We already know what arguments we are going to need; we are mimicking the first two arguments of the ‘ellipse’ routine, but replacing the third and fourth with a single ‘diameter’. So our argument list is going to be ‘float x, float y, float diameter’.

These arguments we can pass straight on to the ellipse routine, passing the diameter for both the width and height arguments, without any additional processing, so our function will look like this:

void drawCircle(float x, float y, float diameter) {
ellipse(x,y,diameter,diameter);
}

A similar function named ‘drawSquare’ would specialise the rectangle routine as follows:

void drawSquare(float x, float y, float width) {
rectangle(x,y,width,width);
}

Now we have two functions, how do we use them? Well, they are used just like any normal routine, you give the function name and arguments in round brackets and the computer executes the function contents at that point in your code.

Let us write a short sketch to draw a circle on top of a square using the functions that we’ve just declared.

void setup(){
size(400,400);
background(255);
drawSquare(200,200,150);
drawCircle(200,200,150);
}

When we execute this we find that it’s not quite what we want.

The circle draws with the (x,y) arguments specifying the centre of the circle, whereas the square draws with the (x,y) arguments specifying the top left corner.

It might be useful to have a further function, say ‘drawSquareCentred’ that uses the (x,y) arguments to specify the centre in the same way as the circle.

Now, the only difference between the normal square and the ‘centred’ square is that an offset is going to be applied to the x and y arguments; so we’re going to use the fact that the functions become part of the system to expand on drawSquare and not repeat the rectangle command.

Our drawSquareCentred function is going to use a ‘local’ variable. A local variable is one that is declared like any other variable, but within the function braces, it cannot be accessed outside of the function itself, it is completely self contained: it is said to have a scope extending to the end of the function.

void drawSquareCentred(float x, float y, float width) {
float halfWidth = width/2 ;
drawSquare(x-halfWidth,y-halfWidth,width);
}

If we now modify our short test sketch to replace ‘drawSquare’ with drawSquareCentred’ we get the result we originally intended: