Unit 7: Functions

This is the archived website of SI 413 from the Fall 2012 semester. Feel free to browse around; you may also find more recent offerings at my teaching page.

This unit is all about function calls. Hopefully your work of implementing function calls in the last two labs has opened your eyes to what's really going on with a function call, but there are even more options than what we have been able to explore in SPL.

Functions fundamentally consist of two parts: the function definition and the call site (wherever the function is actually called). Of course there may be many call sites for a single function definition as well. Making a function call is like saying, "Go back and execute that code I already wrote down."

We are going to examine the different methods of communication between the call site and the function definition, which mostly means talking about how arguments work. Then we'll look at some more advanced uses of functions and other related concepts.

Beginning of Class 19

1 Parameter Passing Modes

Readings for this section: PLP, Section 8.3 intro through 8.3.2 (required), all of 8.3 (recommended)

It is possible for a function to just do the same thing every time, like print some fixed message to the screen. But this is unlikely. Usually, there is some communication between the call site and the function definition. For example, you might write a very simple function that just takes its argument (a number), multiplies it by two, and returns the result. In this scenario, the call site passes in the value of the original number, and the function passes back the value of the result, so there is two-way communication going on.

Function parameters and return values are the primary way in which a program communicates between the function call site (wherever the function is actually called) and the function definition. The other option of course is to use commonly-accessible variables such as global variables: these are set before the function call to pass information into the function, and read after the function call to get information out.

Here is a simple C++ program that shows three ways of writing the "multiply by two" function and printing the result.

int foo1_global;
void foo1() {
  foo1_global = foo1_global * 2;
}
 
int foo2(int in) {
  return in * 2;
}
 
void foo3(int& inout) {
  inout = inout * 2;
}
 
int main() {
  int x=1, y=2, z=3;
 
  foo1_global = x;
  foo1();
  cout << foo1_global << endl;
 
  cout << foo2(y) << endl;
 
  foo3(z);
  cout << z << endl;
 
  return 0;
}

Notice the three basic ways of function communication:

The difference between foo2 and foo3 is called the "parameter passing mode*, which means how the argument is passed from call site to function body.

The most typical option is pass by value; this is what happens by default in C++ and in our SPL language too: the function receives a copy of the argument. Any changes the function makes to the argument are not seen by the call site, because those changes are on a copy that only the function gets to use. This allows any arbitrary expression to be used as the argument, since it will simply be evaluated and then this value is bound to the function's parameter name. The advantages of pass by value are that it is relatively simple to implement, and it clearly defines the communication from call site to function body.

The second primary option for parameter passing is pass by reference, which is supported in C++ by using reference parameters and the & specifier. Some languages, including Perl and Visual Basic, actually use this mode by default. In pass by reference, the function parameter becomes an alias for the argument; any changes the function makes are immediately reflected at the call site. This overcomes some disadvantages of pass by value, namely by avoiding the need to copy large data structures as arguments, and by allowing a function to return more than one thing.

But pass by reference can also lead to programming errors, because it blurs the lines of communication between call site and function body. Is this argument for communicating from call site to function call, or from function call back to call site, or both? We simply can't know without looking at how the function actually works. Different languages provide some compromises for getting the nice aspects of pass by reference without all of the dangers:

In summary, you should know how pass by value, pass by reference, and pass by value/result work, and where we might want to use each one. You should also know that pass by sharing is another option that is used in some languages, and what pass by sharing means.

2 Parameter Evaluation

Readings for this section: PLP, Section 6.6.2 (required), Section 10.4 (recommended)

Now we know how the value of an argument gets passed between the call site and the function body. But what about when the argument is an expression like (x + 2)? When exactly does this get evaluated? Of course there are multiple options! Here are the two basic choices:

The other option is called normal order evaluation, wherein the arguments are not evaluated until they are needed. A related concept (which for the purposes of this course will be the same concept) is called lazy evaluation, which means every argument is only evaluated at most once, but might not be evaluated at all if it's not needed. So lazy evaluation sort of gives the best of both worlds from above - unused arguments are never evaluated, but frequently-used arguments are only evaluated once!

So why not just use lazy evaluation everywhere? Why doesn't C++ use it? The issue comes when expressions have side-effects. For example, in C++ we can have a function call like foo(++x). The clear intent of the programmer in this case is that x should be incremented exactly once when this function call is made. But if we used lazy evaluation, x might not get incremented at all, depending on whether foo actually uses it! This is why lazy evaluation is supported by pure functional languages such as Haskell and ML, where referential transparency is strictly enforced. When there are no side effects, we can always do lazy evaluation. Hooray!

Beginning of Class 20

3 Overloading and Polymorphism

Readings for this section: PLP, Sections 3.5.2, 3.5.3, and 9.4 intro through 9.4.3 (required), all of 3.5, 3.7, and 9.4 (recommended)

Skipped due to superstorm 

4 Operators, Built-ins

5 Macros

Readings for this section: PLP, Section 3.7 (required)