Eve Closures
What is a closure
A closure is a function created by another function and enclosed in it. The closure has read/write access to the variables of the function that created it, which is its context. The context is replicated and bound to every closure instance. A function whose result can differ for the same arguments is stochastic and is marked with the suffix !: name!(). A closure that changes the state it encloses, like a counter, is such a function, so its name ends with !; a closure that always gives the same result needs no !. A function that calls a ! function is not deterministic itself, so its name ends with ! too.
Only functions are closures, and only functions create them. A closure is always a function, never a procedure. The function that creates a closure is a higher-order function and always ends with !, like make_counter! below, even when it could be called deterministic: Eve does not try to optimize these functions. A procedure can also create closures, several of them, and give them out through its @ output parameters (see Closures from a procedure).
Why closures?
Closures are enabled in Eve to offer a convenient way to create functions that return a different result for every call. For a sequence of values, a generator, declared with the keyword generator and using yield, is simpler: see Generators. A closure is a light weight object. Like objects closures are dynamically created at runtime using "new" keyword.
Example: a counter
driver test_closure is
** declare a function that creates a closure
function make_counter!(first: Integer) => (@next!: Function) is
new current := first;
** the enclosed function: the same form as any function, with the name of the result;
** it changes state, so its name ends with !
function next!() => (@result: Integer) is
let current += 1;
let result := current;
return;
return;
process main is
** instantiate two closures
new index1! := make_counter!(0);
new index2! := make_counter!(10);
** interlace the two calls
print index1!(), index1!(); ** 1, 2
print index2!(), index2!(); ** 11, 12
print index1!(), index1!(); ** 3, 4
return;
end test_closure;
Notes
- A closure that keeps a changing state is stochastic: its name ends with
!, and the compiler makes no effort to optimize it. The function that creates it always ends with!too. - Closures are functions, created by functions or by procedures: the creator holds the state, the closure changes it.
- The enclosed function becomes the result, or the output parameter, because it has the same name:
@next!andfunction next!(). - A closure is declared with the same syntax as any function and ends with its own
return;. When it is the last declaration of its creator, tworeturn;lines follow each other: the inner one is indented two spaces more, aligned with the innerfunctionkeyword, the outer one with the outer keyword. This is why indentation is mandatory in Eve. Writing both on one line is possible, but it is not a good practice. - One function can create multiple closures; a procedure can create several at once, as output parameters (next section).
Closures from a procedure
A procedure has no result, but it can create closures and give them out through its @ output parameters. Closures made in the same call share the variables of their creator. The closures are functions.
driver closure_pair is
** two closures that share one counter, given out as output parameters
procedure make_pair(first: Integer, @up!: Function, @down!: Function) is
new current := first;
function up!() => (@r: Integer) is
let current += 1;
let r := current;
return;
function down!() => (@r: Integer) is
let current -= 1;
let r := current;
return;
return;
process main is
new up!, down! :Function;
make_pair(10, @up!, @down!);
print up!(), up!(), down!(); ** 11, 12, 11
return;
end closure_pair;
What a closure remembers
| Case | Result | Marked |
|---|---|---|
| reads a constant or an argument of its creator | always the same for the same arguments | no ! |
| changes a variable of its creator (a counter) | differs at every call | !: next!() |
| reads a variable of its creator that other code changes | can differ | ! |
Closure, object or generator?
| Need | Use |
|---|---|
| one small piece of state and one operation | a closure |
| several operations on the same state, or a visible state | an object (Objects) |
| a sequence of values, one at a time | a generator (Generators) |
| a function to hand to other code | a closure, as a callback (Callbacks) |
Closures and threads
A closure belongs to the task that created it. A closure that changes its state is not thread safe, so the compiler does not let two tasks use the same one (see Thread safety). A task that needs a counter creates its own closure; a counter shared between tasks is an Atomic variable.
Read next: Expressions