Functions

In mathematics, you have functions, i.e. f(x).

f(x) = x^2

f(3) = 3^2 = 9
f(x,y) = y + x^2

f(3,1) = 3^2 + 1 = 10

In computer science, functions are roughly, but not exactly, equivalent to functions in mathematics.

int A_GLOBAL_VARIABLE = 5;

int main( int argc, char** argv )
{
    int parameter1 = 10;
    int parameter2 = 10;

    int result1 = a_pure_function_call( parameter1, parameter2, A_GLOBAL_VARIABLE );

    int result2 = a_non_pure_function_call( parameter1, parameter2 );

    int result3 = A_GLOBAL_VARIABLE * a_pure_function_call( parameter1, parameter2, 1 );
}
Below is the definition of the function "a_function_call". The function will return an integer, and takes two integers as its parameters. The result returned is the product of the two parameters. This is a pure function as it does not have any side-effects.
int a_pure_function_call( int p1, int p2, int p3 )
{
    return p1 * p2 * p3;
}

int a_non_pure_function_call( int p1, int p2 )
{
    return p1 * p2 * A_GLOBAL_VARIABLE;
}

Functions are a key mechanism for abstraction in programming. For example, if a function computes the square root of a passed in number, the code calling is able to assume that the square root of a passed number will be returned, without needing to worry (too much) about the exact algorithm used to determine the square root.

For example, below the function calculates the hypotenus of a right angled triange. And within that function, there is a call to an assumed available function 'squareroot'.

//  h = squareroot( side1^2 + side2^2 ), for a right angled triangle.

float length_of_hypotenuse( float side1, float side2 )
{
    float a = side1*side1 + side2*side2;

    return squareroot( a );
}

For any non-trial program, how data is represented in the program is a concern.

For example, if you are computing a histogram of character frequencies in program input, you will need to store the frequency for each character somewhere - such as an array.

The question arises, if you needed to manipulate that data in some way, how might you do so. Typically, it would be passed to a function that will manipulate the data, and then return the results.

The programming style of the following example, is often referred to as being 'procedural'. In this example, the frequencies array is declared in the main function and passed as an argument to 'total'. Alternatively, it could have been declared as a global variable that is available to total when it is called. While such programming with globals is possible, it should be avoided unless absolutely needed, as it reduceds the purity of functions, and the understandability of programs.

Industry experience led to the realisation that understandability of software and maintaining intellectual tractability is necessary. It was realised that data should be encapulated within modules to reduce the amount of code a programmer has to comprehend in order to understand what is occurring in one part of a program. This resulted in global variables being discouraged, and the increased emphasis on data structures that are encapsulated within modules.

int main( int argc, char** argv )
{
    int frequencies[26];

    frequencies = calculate_frequencies_from_std_in(); // i.e. non-pure function

    int total = total( frequencies );
}
int main( int argc, char** argv )
{
    int x, y; x = y = 0;
}
int main( int argc, char** argv )
{
    int x[10], y[10];

    int midpoint[2];
    
    midpoint = calculate_midpoint( x, y );
}

int[] calculate_midpoint( int x[], int y[] )

In the C programming language, 'struct' declarations allow the definition of structured types, which have component parts referred to as members. In C, a variable that has a structured type can be used in the same way as a native type, i.e., it is allocated on the stack and if passed as a function argument is passed by value unless its address is explicitly passed.

struct _coordinate
{
    int x;
    int y;
};

typedef struct _coordinate Coordinate;

int main( int argc, char** argv )
{
    Coordinate coordinates[10] = {{ 10, 10 }, ... };

    Coordinate midpoint = calculate_midpoint( coordinates );
}

Coordinate calculate_midpoint( Coordinate coordinates[] );

A problem with procedural programming is that it is not always obvious whether a function that uses a type should be defined with the type or elsewhere (such as in another library).

Function pointers can allow a pointer to a function to be passed and stored in data structures within a programs.

typedef struct _coordinate Coordinate;

struct _coordinate
{
    int x;
    int y;
    Coordinate (*calculate)(Coordinate coordinates[]);
};

Coordinate calculate_midpoint( Coordinate coordinates[] )
{
    /* calculates and returns midpiont */
}

int main( int argc, char** argv )
{
    Coordinate (*f)(Coordinate coordinates[]) = calculate_midpoint;

    Coordinate coordinates[10] = {{ 10, 10, f }, { 10, 10, f }, ... };

    f( coordinates );

    coordinates[0].calculate( coordinates );
}

typedef struct _rectangle Rectangle;

struct _rectangle
{
    int side1;
    int side2;
    int (*area)(Rectangle rect);
};
int calculateArea( Rectangle rect )
{
    return rect.side1 * rect.side2;
}

int calculateArea2( Rectangle rect )
{
    return rect.side1 * rect.side1;
}

Rectangle Rectangle_new( int s1, int s2, int is_square )
{
    Rectangle r;
    
    r.side1 = s1;
    r.side2 = s2;
    
    if ( is_square )
    {
        r.area  = calculateArea2;
    }
    else
    {
        r.area = calculateArea1;
    }
}
int main( int argc, char** argv )
{
    Rectangle r1 = Rectangle_new( 10, 5, FALSE );
    
    int area = r1.area( r1 );

    //
    //  Using, function 'bound' to type,
    //  avoids use of function declared elsewhere.
    //
    //  int area = some_other_area_function( r1 );
}
Towards object oriented programming
class Rectangle
{
    int side1;
    int side2;

    int area() { return this.side1 * this.side2; }
}
{
...
Rectange r1 = new Rectangle( 10, 5 );

int area = r1.area();
...
}