Partial functions and lazy evaluation

Partial function application allows easy development of generalised functions, evaluation of expensive functions to be postponed until necessary, and separation of program code into logical chunks to be done without sacrificing efficiency. For more details of the syntax and further examples, see the partial functions tutorial.

In a partial function, only some of the parameters are specified initially, with the remainder being added when the partial function is applied later in the code.

Generalised functions

The filter function returns all members of a list matching a particular predicate. For example, all members of the list numbers between 100 and 200 may be found as follows.

Bool isInRange(Int v) {
    return (v >= 100 && v <= 200);
}

// ...
    inrange = filter(isInRange,numbers);
// ...

However, should multiple ranges need to be selected, then it would be bad practice to produce a separate isInRange function for each range. Instead, a single function that allows the range to be specified should be used. However, this function will not have the type Bool(Int) required to filter a list of integers. Partial application allows the general function to be converted to appropriate specific functions as needed. The code above now becomes:

Bool isInRange(Int min, Int max, Int v) {
    return (v >= min && v <= max);
}

// ...
    inrange = filter(isInRange@(100,200),numbers);
// ...

Lazy evaluation

Lazy evaluation delays the processing of a function until the data is really needed, without requiring the function to know in advance what data will be needed.

Partial functions allow the processing of large data structures to be carried out without entangling the data structure generation with the processing code and also without requiring the entire data structure to be held in memory at once unless absolutely necessary. For example, when generating HTML code for a web application, Kaya creates a tree structure that represents the document and then prints it. For a large document, this may require an impractical amount of memory. However, the appendGenerator function allows the document to be broken down into separate logical segments, with document generation and printing interleaved in the actual execution, but kept separate in the code.

// non-lazy version
// ...
    for comment in comments {
        appendExisting(doc.body,htmliseComment(comment));
    }
// ...

// lazy version
// ...
    for comment in comments {
        appendGenerator(doc.body,htmliseComment@(comment));
    }
// ...

The code is almost identical for the two versions, but in the second, the comments will only be retrieved from the database and converted to HTML when they are about to be printed. Immediately after a comment has been printed, any memory used by may be freed and used by the next comment. In the first case, all comments are retrieved and converted, and stored in memory until the entire document construction and printing is finished. Generally converting an existing algorithm to use partial evaluation is as simple as changing a couple of types from MyType to MyType() and adding a few @ operators in the appropriate places.

An additional advantage is that if, for whatever reason, later events in the code execution mean that the comments are never printed, virtually no time has been taken so far in retrieving and converting the data.

This is generally useful for any data that is generated recursively - for example, a recursive search in the manner of rgrep that stopped after the first match was found would generally be much more efficient if it used lazy evaluation to traverse the directory tree, rather than reading the whole tree into memory and then traversing it. In both cases, an early match means that the traversal may be stopped, but if lazy evaluation has not been used, the tree has already been generated. If the filesystem being examined is slow (perhaps a remote FTP server) then this may be a very noticeable saving.

Using partial functions gives the tree traversing function the ability to build the tree as it traverses it, without any of the generic tree-building code having to appear within the traversing functions. This encourages code separation and reuse.

kaya@kayalang.org | Last modified 13 July 2009 | Supported by Durham CompSoc | Powered by Kaya