Eve Closures

A closure is a function that carries a piece of the place where it was created. Two closures made by the same function have separate states, so one function can create many counters, filters or handlers that each remember their own data.

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! and function 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, two return; lines follow each other: the inner one is indented two spaces more, aligned with the inner function keyword, 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

CaseResultMarked
reads a constant or an argument of its creatoralways the same for the same argumentsno !
changes a variable of its creator (a counter)differs at every call!: next!()
reads a variable of its creator that other code changescan differ!

Closure, object or generator?

NeedUse
one small piece of state and one operationa closure
several operations on the same state, or a visible statean object (Objects)
a sequence of values, one at a timea generator (Generators)
a function to hand to other codea 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.

TODO: explain what happens to the state of a closure when the creating function has returned (the state lives as long as a closure refers to it) and add a test.

Read next: Expressions