Advanced functions

A

Anonymous and partial functions

Partial application of functions

Partial application is a powerful feature that allows you to prepare a function call in advance of it being used. This makes many pieces of code easier to write, and also allows lazy evaluation, where complex calculations are postponed until the answer is needed. Partial application underlies Kaya’s secure web application state handling, and so this tutorial should be read at least briefly before looking at web applications.

import Dict;

Void main() {
    mapping = Dict::new();
    
    record = add@(mapping);
    
    record("key1","value1");
    // etc...
}

This code defines a record function, which is the add function, with the first argument already filled in as a particular dictionary. The record function can now be called with record(x,y) equivalent to add(mapping,x,y). This is a relatively simple use of partial application, merely to save some typing and have clearer code. The next example allows checks on data and execution of the results of these checks to be carried out in the correct order, while also giving a logical order for the source code.

items = getItems();
processing = [];
for item in items {
    if (checkItem(item)) {
        push(processing,processItem@(item));
    } else {
        throw(CheckFailed);
    }
}
for processor in processing {
    processor();
}

Here, the action if the check succeeds is defined next to the check itself, but none of the actions will be carried out unless all the checks succeed. Note also that in this case, the partial application has applied all of the processItem function’s arguments in advance, and so no extra arguments are passed when the function is called. Equally, in some circumstances, it may be useful to partially apply a function with no arguments given.

Bool check(Bool(a) pred, a val) {
    return pred(val);
}

Bool long(String str) {
    return (length(str) > 100);
}

Void main() {
    if (check(long@(),"a string")) {
        putStrLn("It's long!");
    }
}

An alternative way of writing long@() is @long.

Separating code into logical components

[BigData] getData([Int] ids) {
    bigdatas = [];
    for id in ids {
        push(bigdatas,getThisData(id));
    }
    return bigdatas;
}

Void processData([BigData] bigdatas) {
    for bigdata in bigdatas {
        // do something
    }
}

Separating the retrieval and processing functions out is essential for good design if the data may need to be processed in different ways. However, there is a disadvantage if the data is large, because the entire data set must be kept in memory at once. An alternative method is:

Void getAndProcessData([Int] ids) {
    for id in ids {
        bigdata = getThisData(id);
        // do something
    }
}

This method, however, entangles two separate operations, making code reuse more difficult. On the other hand, memory usage will be significantly smaller. Partial application allows the best of both approaches to be combined.

[BigData()] getData([Int] ids) {
    bigdatas = [];
    for id in ids {
        push(bigdatas,getThisData@(id));
    }
    return bigdatas;
}

Void processData([BigData()] bigdatas) {
    for bigdata in bigdatas {
        currentbigdata = bigdata();
        // do something with currentbigdata
    }
}

The code ordering and reusability is as good as the first code, but because generation of the data is delayed until it is needed, memory usage is optimised.

The Lazy List data type from the Lazy module can be used for lazy generation and evaluation of lists.

List = nil() |
       cons(a head, Lazy::List<a> () tail)

Compare this with the standard List from the standard Prelude.

data List<a> = nil |
               cons(a head, List<a> tail);

The difference is that in the lazy list declaration, the tail of the list is a function, rather than a list. This function will be executed when the tail of the list is needed, rather than the entire list being built in advance.

Changing types with partial application

If you have a function that takes a function as an argument, e.g. aBool(Int), then you can use a Bool(...,Int) function instead.

Bool isEqual(Int a, Int b) {
    return a==b;
}

Void main() {
    vals = [1,2,3,4,5];
    if (any(isEqual@(3),vals)) {
        putStrLn("A value equals 3");    
    }
}

This allows more generic functions to be written.

Anonymous (lambda) functions

Anonymous functions can be created with lambda expressions, introduced with a backslash, \, representing a Greek letter lambda. A simple example is the following, which creates a function then calls it immediately:

Void foo()
{
    x = \(a,b) { return a+b; } (4,5);
    putStrLn("x = "+x);
}

A slightly shorter syntax, if a function just returns an expression as above, is:

x = \(a,b) -> { a+b } (4,5);

Of course, usually if you construct a function like this, you would not be applying it immediately. A more common use is in applications of higher order functions such as map. For example, you can double every element in a list as follows:

doubled_xs = map(\(a) -> { a*2 }, xs);

You can use any variable which is in scope in the body of the lambda expression. For example, the following is valid (get is defined in the IO module and reads a line from a file):

mult = Int(get(stdin));
multiplied_xs = map(\(a) -> { a*mult }, xs);

Recent Comments

No comments to show.

Pages