14.1 Generators
We're going to solve the problem with {rand()} we introduced in the last section. The key thing to note is that the definition:
fun myrand: 1 -> int = "rand()" requires header ' ' ;
is in fact not permitted. Functions in Felix are not allowed to have side effects, and random number generators have state which is modified on each call. We saw you can fix this by:
fun mytwice : int -> int = "$1+$1"; var tmp = myrand(); var x = mytwice (tmp);
but this is a bit messy. This is certain to work, because
in Felix the initialisers of a var
are always evaluated
immediately control passes through them, and the result
stored in an object. So using a var
is a method for
forcing eager evaluation. I want you to consider
the following Felix function:
fun mytwice2 (var x: int) => x + x;
When you call mytwice2
Felix guarantees to eagerly
evaluate the argument and assign it to the parameter x
.
If you do not specifically use a var
parameter there is
no such assurance.
Although this solves the problem in this case, it is not a proper solution because the responsibility is put in the wrong place: every function would have to use a var parameter, whereas the real problem is that we broke the rule that functions cannot have side effects.
We could use a procedure. That does solve the problem too:
proc set_rand: &int = "*$1= rand();" requires header ' ' ; var r: int; set_rand (&r);
but this is also ugly!
Here's the right way:
gen myrand_gen: 1 -> int = "rand()" requires header ' ' ; var x2 = mytwice (myrand_gen());
Here the binder gen
specifies a generator.
A generator is a function with side effects.
Any function that gives different results on different
calls and/or depends on any persistent state which it
modifies, or which is modified externally (such as the
current time), must be declared as a generator.
What does this do? Well you already know! Felix creates a temporary variable and evaluates the generator call, then calls your function with the variable's value used in place of the generator call. We say the generator call is lifted out of the expression.
Generator calls are evalued in a deterministic order: Felix scans an expresion by recursive descent of the expression tree and lifts calls out as it sees them. This is roughly order of writing.