Arrays

The declaration of an array in Limbo is similar to that of a list:

	a: array of int;

declares a variable a of type array of int. This declares a variable, initialized to nil, but no storage to go with it, just as

	l: list of int;

declares a list variable l but no elements in the list. Before we can put any elements into the array, we must first create the space to do so. Although lists are built incrementally, arrays must be built all at once.

Arrays are built by an expression called a constructor that looks like this:

	array[20] of int

to create a new array with 20 integers in it. Note that the size of the array must appear in the constructor, but does not appear in the type of the array; variable a may store arrays of any numbers of ints, but at any one time only a single array of a specific size. For example,

	a = array[20] of int;

creates a 20-element array of integers and stores it in a. The number of elements of an array can be discovered with the len operator:

	sys->print("There are %d elements in a\n", len a);

will report that a has 20 elements.

The elements of an array can be accessed like the elements of a string, and the len operator applies to arrays as well as strings and lists. Here is an another version of our routine to compute the average of a set of numbers, this time using an array:

	average(r: array of real): real
	{
		sum := 0.0;
		if(len r == 0)
			return sum;
		i := 0;
		while(i < len r){
			sum += r[i];
			i += 1;
		}
		return sum/real len r;
	}

Note the conversion of len r to real: sum is real and we must convert the integer expression to the same type, as Limbo has no implicit type conversions.

This routine works even if r has not been assigned, because the length of a nil array is defined to be zero, not an error, to simplify programming.

We can simplify this program further:

	average(r: array of real): real
	{
		sum := 0.0;
		if(len r == 0)
			return sum;
		for(i:=0; i<len r; i++)
			sum += r[i];
		return sum/real len r;
	}

First, we use a for loop, which has the form

	for(e1; e2; e3) statement;

and behaves like the following code fragment:

	e1;
	while(e2){
		statement;
		e3;
	}

Any of the expressions e1, e2, and e3 may be missing from a for loop. A missing e2 represents a test that always succeeds.

Next, we declare the variable, i, controlling the loop in the initialization (e1) clause of the for. Finally, instead of i+=1 we use the even shorter equivalent notation i++. The resulting loop,

	for(i:=0; i<len r; i++)

is the standard way to step through the elements of an array.

The values of the elements of newly created arrays of arithmetic types (int, byte, big, and real) are undefined; if the elements are arrays or lists, though, they are initialized to nil. So after executing,

	al := array[5] of list of int;

all 5 elements of al are empty lists.

What if we wanted to initialize all the values of an array when we create the variable, for example to hold the first 5 powers of two? It's done like this:

	a := array[] of {1, 2, 4, 8, 16};

This is a declaration, of course, where the initial value of the variable, the expression

	array[] of {1, 2, 4, 8, 16};

is another form of constructor. The size in square brackets can be omitted since it can be deduced from the number of elements in the braces. The brackets must be present, though. The type of the array elements also needn't be specified: it's derived from the type of the expressions in braces, which must all have the same type.

Lists can be assembled similarly:

	l := list of {1, 2, 4, 8, 16};

declares a variable l holding a 5-element list. (We don't write `list[] of' because lists are not indexed like arrays).

In Limbo, two-dimensional arrays must be built as arrays of arrays:

	mat: array of array of real;

declares a variable to hold a matrix. Unfortunately, this two-dimensional structure is awkard to initialize. One can write

	mat := array[2] of {array[2] of real, array[2] of real};

but this gets clumsy for large matrices, especially when the coefficients should also be initialized. Limbo has a shorthand to make this a little easier. The notation

	array[4] of {* => 0.0}

constructs an array in which every element is the real value 0.0. These can be nested, so we could construct an all-zeros matrix by writing

	mat := array[2] of {* => array[2] of {* => 0.0}};

This => notation in initializers has other forms, allowing the initialization of individual elements of the data. For the full story, see the Limbo Reference Manual.

In many cases, it may be simplest to write the code out. For example, this code builds a diagonal matrix:

	M := 3;
	mat := array[M] of array of real;
	for(i:=0; i<M; i++){
		mat[i] = array[M] of real;
		for(j:=0; j<M; j++)			
			if(i == j)
				mat[i][j] = 1.0;
			else
				mat[i][j] = 0.0;
	}

It's worth studying this example carefully to understand how and when the storage for the matrix is created.

The size of arrays can be defined by a variable or in fact any integer expression; it needn't be a constant. Sometimes it should be a constant, though, and for such occasions Limbo has a form of declaration that declares a constant value. Instead of declaring M the way we did in this example, we could have written

	M: con 3;

which declares M to have the immutable value 3. The expression in a con declaration can have any arithmetic type or be a string. It may even be a variable or other non-constant expression, in which case the constant is set to the value when the declaration is executed. Once the constant itself (here M) is created, though, its value is fixed.

previous next

© Rob Pike and Howard Trickey 1997. All rights reserved.