What the Hell are Procedures and Functions

 


Hello ! Nice to meet you again ! How is the lessons ? It was a great quiz, wasn't it ? Well, time's getting more difficult to pass through. I've got a very busy semester, so that I may not even keep my promise in the Web........ Sorry ! But I keep moving on, don't worry !

Contents :

  1. Simple syntaxes
  2. Scope
  3. Local variables and global variables
  4. Parameters (pass by value and pass by reference)
  5. Ignored return values
  6. Inter-procedure and/or functions call
  7. Nested syntax
  8. Recursive calls
  9. Inter-referenced procedures/functions
  10. Break down problems

OK ! Let's start ! We know that Pascal is a structured language. It means that the problems is divided into steps and subproblems and then is coded in a structure. This division is made through procedures and functions. Well, what the hell are procedures and functions ? It is like a subprogram that has its own begin..end. It is useful when a part of program must be performed more than once in scattered parts of the program. May be it is a bit murky, but let's take a look on its syntax :

Procedure procname;
begin
   :
   { commands }
   :
   :
end;

begin

end.

This is the simplest form of a procedure. (Functions are discussed later) As we can see that procedures are always placed ABOVE the main begin...end. Example :



uses crt;

procedure pause;
begin
  writeln('Press any key when ready ...');
  readkey;
end;

begin
  clrscr;
  writeln('ABC');
  pause;  { Make a call to procedure pause }
  writeln('Hey, let''s pause !');
  pause;  { Make a call to procedure pause }
  writeln('Pause again !');
  pause;  { Make a call to procedure pause }
end.

Run the previous example. Look at this example :


uses crt;

begin
  clrscr;
  writeln('ABC');
  writeln('Press any key when ready ...');
  readkey;

  writeln('Hey, let''s pause !');
  writeln('Press any key when ready ...');
  readkey;

  writeln('Pause again !');
  writeln('Press any key when ready ...');
  readkey;
end.

It gives the same effect as the first example, isn't it ? Which is nicer ? Writing silly same phrases or efficiently using procedures. If you are normal guy, you would love the first example. It is clearer and cleaner. Easy to read. Especially the lines to be replaced with procedures are very long. Procedures, and functions too, make the codes healthier and easier to read. It makes life easier in debugging the program, too.

Procedures could have its own variable, called local variable. The variable is only known inside the procedure. For example :


procedure foo;
var  a : byte;
begin
  a:=10;
  writeln(a);
end;

begin  { main begin...end block }
  foo;
  a:=15;     { This will be an error, since a is only known inside foo }
end.

Global variable is a variable that is known throughout the program and its procedures or functions. Example :


var
  b : byte;

procedure foo;
var  a : byte;
begin
  a:=10;
  b:=15;  { This is legal }
end;

begin  { main begin...end block }
  foo;
  a:=15;  { This is illegal }
  b:=5;   { This is perfectly legal }
end.

If a local variable is declared inside a procedure with the same name as the global variable, the global variable is overrided. For example :


uses crt;
var
  a : byte;  { This is the global variable }

procedure foo;
var a : byte;
begin
  a:=15;
  writeln('foo is ',a);
end;

begin
  a:=10;
  writeln(a);
  foo;
  writeln(a);
end.

Functions are pretty much the same, except it has a return value. It just like mathematic functions. Syntax :

function funcname : returntype;
begin
   :
   { commands }
   :
   :
end;

begin

end.

Functions are also placed before the main begin...end. Example :


uses crt;

function yesno : boolean;
var c : char;
begin
  write('Are you sure (Y/N) ? ');
  repeat
    c:=upcase(readkey);      { Make it upcase letters }
  until (c='Y') or (c='N');
  writeln(c);
  if c='Y' then yesno:=true else yesno:=false;
end;

var
  x : boolean; { Don't be tricked, this is a LOCAL variable of main block }
begin
  clrscr;
  writeln('Discard all changes made');
  x:=yesno;
  if x=true then
    writeln('Changes discarded !');
  else
    writeln('Changes saved !');

  writeln('Quitting');
  x:=yesno;
  if x then 
  begin
    writeln('Process terminated !');
    halt;
  end;
  
  writeln('Quit cancelled !');
  writeln('Continuing process ...');
end.

Can you see a big difference between procedures and functions ? Use function if we want to get a return value. Suppose the yesno function. It returns true if user presses y and returns false if user presses n. Yes, yes, it is pretty complex. But, try to understand. Or... try to understand this simple excerpt : (This is only the begin...end part. All above is the same as the previous one.)


begin
  x:=yesno;
  if x then
    writeln('You answered "Yes"')
  else
    writeln('You answered "No"');
end.

OK ! Any questions ? Send them to me ! Let's continue...

In a well-structured program, we use global variables as minimal as possible and use the optimum amount of local variables. How can we ? Well, practice a lot ! Sometimes, we need some value to be known inside a procedure. In that case, we need parameters. We can put parameters just beside the procedure's or function's name, suppose :


procedure myproc (a:integer; b:real);
begin
  :
  :
end;

function abc (a,b : integer; c:byte) : longint;
begin
  :
  :
end;

Let's see it in 'real life' !


uses crt;

procedure makewin(x1,y1,x2,y2 : byte);
var
  i,j : byte;
begin
  { top }
  gotoxy(x1,y1); write(#201);
  for i:=x1+1 to x2-1 do write(#205);
  write(#187);

  { middle }
  for i:=y1+1 to y2-1 do
  begin
    gotoxy(x1,i); write(#186);
    for j:=x1+1 to x2-1 do write(' ');
    write(#186);
  end;

  { bottom }
  gotoxy(x1,y2); write(#200);
  for i:=x1+1 to x2-1 do write(#205);
  write(#188);
end;

begin
  makewin(1,1,30,8);
  makewin(10,10,60,18);
end.

Simple example above tell us about making a window in a coordinate (x1, y1) to (x2, y2). With this engine, we could make many windows with just entering coordinates and the SAME code. Parameters makes us easier to customize the procedures and functions. Let's see this example :


function factorial(n : byte):longint;
var
  i      : byte;
  result : longint;
begin
  result:=1;
  for i:=1 to n do 
    result:=result*i;
  factorial:=result;
end;

var
  x : byte;
begin
  writeln('Enter a value : '); readln(x);
  writeln(x,'! is ',factorial(x));
end.

Those previous examples are widely used in programming life. Let's look at this :


procedure foo(a : byte);
begin
  writeln(a);  {15}
  a:=10;
  writeln(a);  {10}
end;

var
  x : byte;
begin
  x:=15;
  writeln(x);  {15}
  foo(x);
  writeln(x);  {Still 15}
end.

It outputs :

15
15
10
15

At first, x is 15, then passed to procedure foo as passing parameter a. Eventhough it is legal to modify a inside foo, the value of x is unaffected anyway. This type of passing method is called PASS BY VALUE. How about this :


procedure foo(var a : byte); { See the difference ? }
begin
  writeln(a);  {15}
  a:=10;
  writeln(a);  {10}
end;

var
  x : byte;
begin
  x:=15;
  writeln(x);  {15}
  foo(x);
  writeln(x);  {10}
end.

It outputs :

15
15
10
10

If we add var before a as passing parameter, the value of x is changed whenever a is changed. This type of passing method is called PASS BY REFERENCE.

We must pass by value if the parameters are not necessarily be changed in the procedure. If the parameters need changing and the change is important to be known by the caller, use pass by reference.

In Borland Pascal 7.0, we could omit the return values of a function. In our examples so far, we use readkey, right ? Readkey is actually a function but a built-in one. Before version 7.0, we must take a return value of it (readkey), like this : c:=readkey;. But, from version 7.0, we could omit it by just calling readkey like this : readkey;

You CAN call another procedure inside a procedure or function as long as the caller procedure/function lies below it. Example :


procedure a;
begin
  :
  :
end;

procedure b;
begin
  :
  a;       { legal }
end;

begin
  :
end.

It is legal for procedure b to call procedure a. But procedure a can NEVER call procedure b. It is ILLEGAL. Function can call procedures, procedures can call functions, functions can also call other functions as long as the caller is below the target (the one which is invoked). So, you'd better arrange the procedures and functions you might have.

If the if-structure and loop structures could nest each other, can procedures and functions nest each other ? YES, IT COULD ! Example :


procedure e;   { e cannot access a, b, c, and d }
begin
  :
end;

procedure a;
   procedure b;
   begin
     c;  {illegal}
     e;  {legal}
   end;
   procedure c;
   begin
     b;  {legal}
     e;  {legal}
   end;
begin
  :
  b; {legal}
  c; {legal}
  e; {legal}
end;

procedure d;
begin
  :
  b; {illegal}
  c; {illegal}
  a; {legal}
  e; {legal}
end;

begin
  :
  b; {illegal}
  c; {illegal}
  a; {legal}
  d; {legal}
  e; {legal}
end.

Procedure c can call procedure b since c is below b, but procedure b cannot call procedure c. Procedure a, whose begin..end block below procedure b and procedure c, can call procedure b and c. Procedure b and c cannot be access ed by procedure d and main block. So, nested procedure can only be accessed by their brothers/sisters (among the same level of nested procedure), and their parent (the procedure who endow them). Accesses between brothers follow the rule of normal procedure calling.

Practice a lot !

How about calling ourselves ? Well, IT IS PERFECTLY LEGAL too !! Example :


procedure a;
begin
  a;
end;

begin
  a;
end.

But it will say error since it call itself ENDLESSLY. The method of calling oneselves is called RECURSIVE CALLS. In recursive calls, we must :

  1. Provide terminating conditions.
  2. Be extremely careful in coding.
  3. Not use too many recursive calls, say 50,000 times.
If not, you could find these symptoms :
  1. Hang / stopped responding.
  2. Computer "auto-murmur".
  3. Beeping eratically.
  4. Auto-reboot.
  5. Auto-format (no...no ! Just kidding !)
  6. Other unexpected result

Let's see a very good example of recursive calls : It's factorial. See the non-recursive method of factorial function above, then see this recursive version :



function factorial (n:byte):factorial;
begin
  if n<2 then     { This is the terminating condition }
    factorial:=1;
  else
    factorial:=n*factorial(n-1); { This is the recursive part }
end;

var
  x : byte;
begin
  writeln('Enter a value : '); readln(x);
  writeln(x,'! is ',factorial(x));
end.

If x = 5,
At first call, n = 5. Factorial:=5*factorial(4); => need second call
Second   call, n = 4. Factorial:=4*factorial(3); => need third call
Third    call, n = 3. Factorial:=3*factorial(2); => need fourth call
Fourth   call, n = 2. Factorial:=2*factorial(1); => need fifth call
Fifth    call, n = 1. Factorial:=1;        => inserted back to above so
4th call becomes : Factorial:=2*1; (=2)    => inserted back to above so
3rd call becomes : Factorial:=3*2; (=6)    => inserted back to above so
2nd call becomes : Factorial:=4*6; (=24)   => inserted back to above so
1st call becomes : Factorial:=5*24; (=120)

As you may see that factorial in recursive method is simpler than ever. Suppose you miswrite n-1 to n, the terminating condition would never be functional. So, it will loop forever ! Be careful !

Well, we come to a quite advanced matter. It is sometimes used though. Inter-referenced calls. It means that 2 procedures or functions could call each other.



procedure a;
begin
  b;   { illegal }
end;

procedure b;
begin
  a;
end;

As you may see, calling a from b is legal, but calling b from a is illegal. It sometimes necessary for a to call b and for b to call a. The real-life problem is like context-sensitive help. Sometimes the description, when it is pointed or clicked, it call the index. But also, the index has to call its description after a topic is picked. The solution is : FORWARD CALL. Example :



procedure b; forward;

procedure a;
begin
  b;    { now legal }
end;

procedure b;
begin
  a;
end;

Use the statement forward to make b visible to a, so a can call b. It is more dangerous than recursive call. If you are not careful, you may find the same symptoms as recursive call did. What you must do is also the same as in the recursive call. Just beware : Calling procedures uses a part of memory called stack. Reciprocal calls wastes stacked much faster than recursive calls. If you run out of stack, your program would stuck.

How could I break down complex problems ? How good your ability in breaking problems is actually depends on your experience in programming. But, I can only tell you this :

  1. Identify your problem correctly.
    Suppose you have to make a program to record employee data for a bank. What is it for ? Employee data for just an information or for salary statistics or what ?
  2. Separate one process from another.
    Suppose you do employee data for information. The processes are record it to disk, edit data, delete data, sort data, or print and display data, or search data.
  3. List the solution steps of each process.
    Suppose you first do record data. You take input from keyboard and record it to disk. You ask if user wants to input again or not.
  4. List the data requirements.
    Employe data may require name, position, residence, office location, civil status, age, sex, years of service, and so on.
  5. Determine the output requirements.
    You may design the output like a list of employee data. It is useful for comparison reason. Or may be you would love to make it like an archive form for complete document. Or you ask the user and they can choose.
  6. Construct an algorithm to do the process.
    Suppose taking data from keyboard, may sound : input name, input age, input position, and so on. Then prepare file, record the data to disk, close the file. Ask user if they want to input some more. If they do, repeat the whole process, otherwise quit.
  7. Use your creativity to expand the input, process and output.
    Don't stick the model from input to output. You may add your creativity. Client may want the output format exactly the same as they wish. Perhaps you could give suggestions in arranging them, or perhaps if you think some data is important to display or print, go tell the client.
  8. Think of all other useful things to add in program.
    Suppose you want to add the feature of Auto-Correct, to minimize the human-error. You probably record some usual names, like Jim, Dave, and so on. If user types Dabe, for example, your program can give suggestion to change it to Dave. Yeah, somethings like that.
  9. Implement it, step by step.
    Don't let all dreams be dreams. Let it comes true.
  10. Combine all the programs to tackle the problem.
    All the module of each process should be gathered. You may want to generalize your input procedure to fit in each module to save energy and time. Combine them using menus. And don't forget to make finishing touches, to make the software looks great.

Well, that's one of the complex problems. You may follow the guidelines. OK, that's all for today ! Have a good quiz ! =)


Where to go ?

Back to main page
Back to Pascal Tutorial Lesson 1 contents
To the quiz
Back to Chapter 5 about looping
To Chapter 7 about arrays
My page of programming link
Contact me here

By : Roby Joehanes, © 1996, 2000