• Aucun résultat trouvé

Non-Functional Constructs in Id

Typing Non-Functional Constructs

5.1 Non-Functional Constructs in Id

Id has two classes of non-functional objects,

I-Structures

and

mutex

objects [9, 48]. We will not concern ourselves with the run-time dynamic semantics of these objects except for the fact that they behave like storage locations, in that they can be allocated without specifying their contents, be assigned to, or be queried for their contents1. For type-checking purposes, they are similar to the \ref" construct of ML which generates a storage location pointing to an object that can later be dereferenced for its value or assigned a new value.

We will informally describe the use of some non-functional constructs in Id and the asso-ciated requirements they place on the type-checker2. Consider the following Id example called wavefront3 taken from [9],

The function I matrix allocates and returns a fresh, uninitialzed I-structure matrix with type (I Matrix *0), given its 2-dimensional bounds. Note that the type of the elements of the matrix is not known at this point. This type has to be obtained via the assignment operation occuring elsewhere in the program. Moreover, the type-checker must make sure that all locations of the matrix are lled with elements of the same type, since arrays are meant to be homogeneous. The problem is non-trivial since allocation of an array can be widely separated from its assignment in the program text.

It is possible to write the above example functionally using special array specication syntax called array comprehensions4. Here, we simply specify an array of values all together. There is

1The reader should note here that the dynamic semantics of I-structures and mutex objects is very dierent from conventional storage locations. But here, we are interested only in their static semantics.2

All our examples deal with I-structure objects, similar examples can be constructed involving mutex objects.

3The function I matrixdynamically allocates an uninitialized I-structure matrix. This is subsequently lled in three independent regions. The left and the upper boundary are initialized to 1 and the computation of the remaining elements depend upon their neighbors to the left and above. Given the parallel semantics of Id, all loop iterations execute in parallel, lling the matrix from the top left corner to the bottom right in a wave-like fashion, hence the name4 wavefront.

The footnote on page 119 explains the syntax of array comprehensions. Non-strict semantics of Id allows the use of an array before it is fully specied.

no notion of assignable locations, the value of the array at each index is specied at the time

It is meaningless to assign a value to a location of a functional array just as it is meaningless to assign a value to the integer 1. The type-checker is able to detect such errors, since functional arrays have a dierent type from non-functional arrays which are assignable. Even though we keep the two kinds of arrays separate, for the sake of simplicity and eciency, we may want to implement the functional arrays using assignable I-structure arrays. We will come back to this point in section 5.3.

We show another example to illustrate that a non-functional implementation may consid-erably improve the eciency of an otherwise functional operation. Consider the problem of appending two lists; a recursive, functional version is shown below.

def append nil l2 = l2

| append (x:xs) l2 = x:(append xs l2);

The above function recursively calls itself while traversing down the rst list, implicitly recording each of its elements in a separate activation. At the end of the list, it pops the stack of activations one by one, cons-ing the elements over the second list. This is a very expensive implementation as it requires as many procedure invocations as the length of the rst list.

A tail-recursive version shown below, is more ecient but still allocates too many cons-cells, because it has to reverse the rst list before cons-ing up its elements onto the second list, so that its elements are added in the right order.

def append l1 l2 = loop (reverse l1) l2;

def loop nil l = l

| loop (x:xs) l = loop xs (x:l);

Instead of these functional solutions, below we show an implementation using a technique in Id known as \open lists"5. The idea here is to be able to grow a list at its end rather than at its front. There is no functional way to do this. Using open lists, we can eciently copy the

rst list as we traverse it down in a loop and stick the second list at its end in order to close it and make it into a valid functional list6. Only as many cons-cells are allocated in the process as necessary in copying the rst list.

Def append l1 l2 =

The function OLcons allocates a fresh I-structure cons-cell which initially has the type

(list *0). Again, the type of the list element to be stored in the cons-cell is not known at the time of this allocation. This is determined implicitly via the assign operation over that cons-cell. Moreover, the type-checker must make sure that lists constructed in this manner are homogeneous and furtherassignoperations on its cons-cells are disallowed because we intend theappendoperation to return functional lists.

It should be clear that in general, such storage allocating functions destroy the referential transparency of the program and hence render it non-functional. A simple example is shown below.

The expression e1 allocates two separate arrays, returning a pointer to each in a tuple, whereas e2 attempts to eliminate the common subexpression (i array (0,1)) and allocates

6This example uses assignable I-structure cons-cells created via the allocator function OLcons. The special identier \ " (underscore) indicates that the particular eld of the allocated cons-cell is initially empty. The binary operationassignis the equivalent of the assignment operation in this case. It stores the specied values from its second argument pattern into the cons-cell given by the rst argument. Assignment into a particular slot of the cell may be avoided by using the special identier \ " in the pattern specication. The next-ied identier bindings within the body of the loop specify the values of those identiers for the next iteration of the loop.

only one array, returning a tuple of pointers that are aliased to the same array. Such a trans-formation does not aect the meaning of functional programs (say, if numbers were present instead of I-structure arrays), because functional programs are referentially transparent. But the above pair of expressions have dierent meanings as shown by their use in the following simple context.

def context (x,y) = f x[1] = 1 in

y[1] g; context e1 =) Undened!

context e2 =) 1

Referencial transparency is a very useful property for compiler optimizations, so we would like to preserve it for the rest of the program even if some parts of it are non-functional.

Therefore our goal is two-fold,

1. Non-functional constructs are desirable because there may be some algorithms that are inherently non-functional or their functional versions do much worse in execution speed as the appendexample shows. Therefore, we must provide sound type inference rules for them in our language.

2. We should be able to restrict the scope of visibility of these constructs to clearly demar-cated non-functional regions, so that functional optimizations can proceed unhindered in the rest of the program.