Choice Of Control Structures

Though not recognized as that by all programmers the flow control structures themselves are first class indicators of the codes workings. We consider three important cases here.

  1. while vs. for,

  2. if vs. select, and

  3. strict block structure vs. premature return.

while/for

Expressed in words a for loop tells us:

Whereas the while loop says:

Corollary: The termination condition of a while must be influenced in the loop's body.

Compare the next to code snippets, the first calculating the average of a vector of numbers, the second searching zeroes of a given function.

    values = [ 1.0 2.0 3.0 4.0 5.0 ];
    average = 0;
    n = size(values, 'c');             // line 3
    for i = 1:n
        average = average + values(i)
    end;
    average = average / n

Form line 3 on, we know the number of iterations, n; from the problem we know that nothing will change that. Thus a for-loop is adequate.

    deff('[y, dy] = fun(x)', ..
         'y = -0.5 + 1.0 / (1.0 + x^2), ..
          dy = -2.0 * x / (y + 0.5)^2');
    x0 = 0.76;

    [y, dy] = fun(x0);
    while abs(y) > sqrt(%eps)
        x = y / dy - x0;
        x0 = x;
        [y, dy] = fun(x)
    end;

Assuming that the function fun and the start guess x0 is given by the user, we do not know how many loops it will take for Newton's algorithm to converge, if it does converge at all. (In the example it does.) Here, the while-loop expresses this lack of a-priori knowledge.

if/select

The relationship between if and select bears similarity with while and for respectively. In a select clause the different cases are known – and spelled out explicitely – before the thread of control enters the construct. There is a one to one relationship between the states of the selecting expression and the case branch taken. The else branch in a select works exactly as the else in an if.

Example 3-2. Function fibonacci

        function f = fibonacci(n)
        // return n-th Fibonacci number

        select n
        case 0 then
            f = 1
        case 1 then
            f = 1
        else
            f = fibonacci(n - 1) + fibonacci(n - 2)
        end
    

The selecting expression is not restricted to scalars. For example, vectors work too:

Example 3-3. Function shape4

        function s = shape4(m)
        // classify a 2x2 matrix according to its shape

        select abs(m) <= %eps
        case [%t %t; ..
              %t %t] then
            s = "empty"
        case [%t %f; ..
              %f %t] then
            s = "diagonal"
        case [%f %f; ..
              %t %f] then
            s = "upper triangular"
        case [%t %t; ..
              %f %t] then
            s = "lower triangular"
        case [%f %f; ..
              %f %f] then
            s = "full"
        else
            s = "general"
        end
    

An if clause is more flexible than a select clause, but at the price of being less expressive. Whenever a whole range of values has to be covered the if clause is the only way to go.

Example 3-4. Function mysign

        function y = mysign(x)
        // re-write of the sign-function,
        // taking floating-point precision
        // into account

        if abs(x) < %eps
            y = 0.0
        elseif x >= %eps
            y = 1.0
        else
            y = -1.0
        end
    

Strict Block Structure/Premature Return

The paradigm of structured programming is: "Every block has one and only one entry point." That's it! Nothing is said about the number of exit points. The purists often misinterpret the paradigm, demanding a single exit point, too. We prefer our freedom and choose whatever we find adequate to the problem.

Here are two different implementations of an algorithm calculating the factorial of a given integral number.

    function y = fact_block(x)
    // faculty of x
    // block-structured version

    select x
    case 0 then
        y = 1
    case 1 then
        y = 1
    else
        y = x * fact(x - 1)
    end

The two special cases 0, and 1 are tested separately and the general case is handled in the else branch.

    function y = fact_early_ret(x)
    // faculty of x
    // early-return version

    if x >= 0 && x <= 1 then
        y = 1
        return
    end

    y = x * fact(x - 1)

This version immediately returns after having treated the special cases, leaving the general case to the "rest" of the function. In this very short function the advantages of the early return are not striking, however they are if there are many special cases to be handled. The "rest" of the function can then concentrate on the core of the problem without being obscured by deeply nested conditionals.