Introduction to Programming Languages/Parameter Matching
Virtually every programming languages provides developers with the capacity to build small pieces of code that can be activated from different parts of the program. This abstraction has different names in different languages. Just to name a few examples, Haskell provides functions. C, in addition to functions, provides procedures, which are functions that do not return values. Java and C# provides methods. Prolog provides inference rules. For simplicity, in this chapter we call all these abstraction functions. In every case, the calling code must pass parameters to the called code. There are many different ways to pass these parameters, and these policies are the subject of this chapter.
Parameter Matching
[edit | edit source]Many authors name the parameters used in the declaration of the functions as formal parameters. As an example, the function div, below, written in python, has two formal parameters, dividend and divisor:
def div(dividend, divisor):
r = dividend / divisor
print "Result = ", r
When the function is called, we pass to it the so called actual parameters. For instance, in the code snippet below, we are passing to our function div two parameters, the variable x, and the constant 2.0:
>>> x = 3.0
>>> print div(x, 2.0)
Result = 1.5
In this example, the value stored in the variable x, the actual parameter, is used to initialize the value of divident, the formal parameter. In this case, the matching between formal and actual parameters is positional. In other words, actual parameters are matched with formal parameters based on the order in which they appear in the function call. Although the positional method is the usual way to match formal and actual parameters, there is another approach: the nominal matching.
>>> x = 3.0
>>> div(divisor=2.0, dividend=x)
Result = 1.5
This time, we explicitly name the actual parameter matched with the formal parameter. Nominal matching is present in a few programming languages, including ADA.
Parameters with Default Values
[edit | edit source]Some programming languages give the developer the possibility to assign default values to parameters. For instance, Lets consider the same python function div seen before, but this time with a slightly different implementation:
def div(dividend=1.0, divisor=1.0):
r = dividend / divisor
print "Result = ", r
Each formal parameter, in this example, is assigned a default value. If the actual parameter that corresponds to that formal parameter is not present, the default value will be used. All the calls below are possible:
>>> div()
Result = 1.0
>>> div(dividend=3.2, divisor=2.3)
Result = 1.39130434783
>>> div(3.0, 1.5)
Result = 2.0
>>> div(3.0)
Result = 3.0
>>> div(1.5, divisor=3.0)
Result = 0.5
There are many languages that use default values. C++ is a well-known example in this family. All the calls below, for instance, give back valid answers. If the number of actual parameters is less than the number of formal parameters, then the default values are used. The matching, in this case, happens from the leftmost declaration towards the rightmost declaration. So, default values are applied to the formal paramters from the rightmost declaration to the leftmost.
#include <iostream>
class Mult {
public:
int f(int a = 1, int b = 2, int c = 3) const {
return a * b * c;
}
};
int main(int argc, char** argv) {
Mult m;
std::cout << m.f(4, 5, 6) << std::endl;
std::cout << m.f(4, 5) << std::endl;
std::cout << m.f(5) << std::endl;
std::cout << m.f() << std::endl;
}
Functions with variable number of arguments
[edit | edit source]There are programming languages that let the developer to create functions with a variable number of arguments. The most well-known example is C, and the canonical example in this language is the printf function. This procedure receives as input a string describing the output that will be produced. By analyzing this string, the implementation of the printf function "knows" which are the next parameters that it probably will receive. Probably, because there is nothing in the C compiler that forces the developer to pass the right number of parameters and use the right types. The program below, for instance, is very likely to cause a runtime error due to a segmentation fault.
#include "stdio.h"
int main(int argc, char **argv) {
printf("%s\n", argc);
}
The language C provides developers with a special syntax, and some library functions that let them create functions with a variable number of parameters. For instance, the function below is a function that can sum up four or less integer numbers. The first parameter is expected to be the number of arguments that the function must expect. The variable ap, is a data structure that points to each argument, as passed to the function foo. Inside the loop, we use the macro va_arg to obtain a pointer to each parameter. These pointers are stored in the array arr. Notice that va_arg is an example of how parametric polymorphism can be simulated in C. This language does not have type constructors, that is, it is not possible to pass a type to a function. However, we can simulate this capacity via macros.
#include <stdio.h>
#include <stdarg.h>
int foo(size_t nargs, ...) {
int a = 0, b = 0, c = 0, d = 0;
int *arr[4];
va_list ap;
size_t i;
arr[0] = &a;
arr[1] = &b;
arr[2] = &c;
arr[3] = &d;
va_start(ap, nargs);
for(i = 0; i < nargs; i++) {
*arr[i] = va_arg(ap, int);
}
va_end(ap);
return a + b + c + d;
}
int main() {
printf("%d\n", foo(0));
printf("%d\n", foo(1, 2));
printf("%d\n", foo(2, 2, 3));
printf("%d\n", foo(3, 2, 3, 5));
}
Again, there are no guarantees that the correct number of arguments will be passed to foo. As an example, the program below will print the value 10. The answer for this behavior is on the binary representation of the variable u. This variable occupies 8 bytes in memory, and each half is seen as a parameter.
int main() {
// 10,00000000000000000000000000000011
unsigned long long int u = 8589934595;
printf("%d\n", foo(2, 5, u));
}