Using Nasal functions: Difference between revisions

Jump to navigation Jump to search
(→‎Function closures: new section)
Line 257: Line 257:
</syntaxhighlight>
</syntaxhighlight>


===Function closures===
Each time a func {...} expression is evaluated, a new function object is created that is bound both to the namespace that it was evaluated in and to the currently executing function, which in turn is bound to an outer namespace and possibly another function/closure. This creates a list of namespaces, or closures, that represents the different possible scopes for a symbol name: a symbol can either be resolved in the local namespace of the function (created while executing that function) or in one of the successive closures. Since a closure is just a reference to a hash (the namespace), an outer closure may still exist even if it isn't accessible from a global namespace or the local namespace of an executing function (think stack frames in C), since it is kept accessible by the reference from the function object. For example:


<syntaxhighlight lang="php">
var outer = {};
var generator = func(mid) {
    return func(local) {
        debug.dump(outer);
        debug.dump(mid);
        debug.dump(local);
    }
}
generator([[],[]])(99); # prints {} [[],[]] 99
</syntaxhighlight>


As evidenced by the example, a function can remember the local namespace of the function that created it (e.g. the local namespace of <tt>generator</tt>) and can also remember the same closures that the generator had (which in this example would be both the namespace that contains the outer variable as well as the global namespace, whatever that might be).
====Misuse of closures====
What's wrong this this code?
<syntaxhighlight lang="php">
var result = [];
for (var i=0; i<10; i+=1)
    append(result, func print(i));
foreach (var fn; result)
    fn(); # should print the numbers 0-9
</syntaxhighlight>
The problem is that each function generated using this method keeps a reference to the namespace as its closure, not a copy of the namespace. This means that when looking up the symbol i from one of the functions, its current value is used, not its value at the time that the function was made. Thus as the variable i gets modified by the for loop, calling one of the generated functions will print ''that'' value of i – and as i is 10 after the end if the loop, we will just see 10 printed 10 times.
How to fix this? The obvious fix is to keep a reference to a copy as the closure and there are two ways to make that happen. The first way is using a generator function, and the second way uses bind(). The first way is really simple:
<syntaxhighlight lang="php">
for (var i=0; i<10; i+=1)
    (func {
        # this is an anonymous closure that will be able to "save" the value of i
        var i = i; # make a copy as a local variable
        append(result, func print(i)); # this function will use the local i that we saved
    })(); # call immediately
</syntaxhighlight>
Or one could write it like this (which I prefer for minute reasons related to the garbage collector/GC):
<syntaxhighlight lang="php">
# arguments are local variables, so this form can do the same thing:
var generator = func(i) return func print(i);
for (var i=0; i<10; i+=1)
    append(result, generator(i));
</syntaxhighlight>
The other way (which produces a nearly identical result) uses the builtin bind() function. When a func {...} expression is evaluated, there are two Nasal variables that are involved: one is a naCode and the other is a naFunc. The naCode is stored as a constant for the function and it stores information about the arguments and the body; the naFunc is simply a wrapper that makes the naCode executable and gives it a closure. Each time the VM evaluates the func expression, this creates another naFunc from the naCode base with a unique closure. If you call <tt>bind(fn, caller(0)[0], caller(0)[1])</tt> (where fn is the function you just created), you essentially do nothing – as the naFunc is, by default, bound to the caller like so. However, changing the second argument to a different hash, say a unique one for each iteration, will change the outer namespace of the function while keeping the rest of the closure intact (this last part is important for keeping the global namespace as a closure, so that the print function can be found). For example, using an empty hash with our first example snippet and bind() will produce an undefined symbol error, since we effectively removed the closure that contained the i variable. But if we create a hash such as <tt>{i:i}</tt>, then we will create a faux-namespace with the i variable – but this variable won't be affected by the for loop, and thus we will have accomplished our goal. This is the completed code:
<syntaxhighlight lang="php">
for (var i=0; i<10; i+=1)
    append(result, bind(func print(i), {i:i}, caller(0)[1]);
</syntaxhighlight>
(for the record, bind returns the function it just rebound, even though it works via mutation, so essentially it is a convenience return of the first argument.)


===Functional programming, higher order functions, generators (advanced concept) ===
===Functional programming, higher order functions, generators (advanced concept) ===
395

edits

Navigation menu