8 Functional values
Functions are first class citizens. This functional programming slogan
means that you can write a function, that takes function as argument and
returns function. This is called higher order function. As this might sound
a bit magic, let's make it look like real voodoo: :)
8.1 Example
opt_struct <'a>list {
'a data;
<'a>list next;
}
*(void (<'a>list)) mapper(*(void ('a)) f)
{
void for_all(<'a>list lst)
{
while (lst != null) {
f(lst.data);
lst = lst.next;
}
}
return for_all;
}
This function takes function f
operating on items, and returns function
that applies f
to all elements of list.
Ok, now some explanations. The key thing to note is functional type
notation. *(void ('a))
is function that takes single argument,
of any type, and returns no value. Similarly
*(string (int, <'a>list))
is function, that takes int
and <'a>list
parameters, and returns string
.
8.2 That's weird, I can't get it!
First step is to use higher order functions, next is to write them. Gont
standard library includes several modules involving higher order functions.
One such example is List::iter()
function. It is defined as:
void List::iter(*(void ('a)) f, <'a>List::t lst);
Let's say you have list of integers and want to print it out:
<int>List::t some_list;
// ... here it is assigned some value ...
void print_element(int el)
{
print_int(el);
print_newline();
}
List::iter(print_element, some_list);
List::iter()
applies given function to each element of list.
8.3 More examples
// Call passed function f on each element of the list l
// starting from head.
void iter(*(void ('a)) f, <'a>list l)
{
while (l != null) {
f(l);
l = l.next;
}
}
// Call function f on each element of the list l,
// collect results as a list (of possibly different type)
// and return it.
<'b>list map(*('b ('a)) f, <'a>list l)
{
<'b>list o = null;
while (l != null) {
o = {data = f(l.data), next = o};
l = l.next;
}
return o;
}
Following functions are defined in Gont standard library:
string itoa(int);
void print_string(string);
We can use them with our iter
and map
:
<int>list il = { data = 1, next = { data = 2, next = null }};
<string>list sl = map(itoa, il);
iter(print_string, sl);
This will print ``12''.
8.4 MLish variations about defining functions
Nested function definitions (for example the for_all
function) are
shortcut to defining functional variables and initializing them, so our
example could look as:
*(void (<'a>list)) mapper(*(void (int)) f)
{
*(void (<'a>list)) for_all;
for_all = fun void (<'a>list lst) {
while (lst != null) {
f(lst.data);
lst = lst.next;
}
};
return for_all;
}
Or even:
*(void (<'a>list)) mapper(*(void (int)) f)
{
return fun void (<'a>list lst) {
while (lst != null) {
f(lst.data);
lst = lst.next;
}
};
}
fun is keyword. After fun comes return type,
then list of parameters, along with their types, just like in regular
function definition in C. Then comes body of function -- either block
or expression in parenthesis.
8.5 Omitting return keyword
Special extension is supported, useful when dealing with functional
values. Whenever function body (sequence of statements within { }
)
should appear, single expression within ( )
can be supplied. It is
passed to return statement, which is only statement in function body. So:
fun int (int a, int b) (a + b)
is equivalent of:
fun int (int a, int b) { return a + b; }
8.6 Closures
This section is for curious folks, who always want to know, how things
work.
Local functions needs to remember variables from enclosing function, at
the time they were defined. For example for_all
function from
our example needs f
parameter of mapper
. However
for_all
cannot rely on being called when mapper
activation
record is still on the stack.
Therefore local function are stored as pair of pointer to function
and pointer to special closure structure. Closure of a function holds
all variables defined in it, that might need to be accessed by local
functions. Local functions, in generated code, are always passed
closure to enclosing function as first argument.