A closure is a block of code that is treated as code, not as a result of code. Closures are actually used quite frequently in your code already, and you just aren't aware, however, with the closure architecture, you yourself can harness the power of a closure, and in fact they are required to be used in many of the newer, more powerful functions.
So, what is a closure? You can read the wikipedia article
if you wish, but it's fairly complex. So, instead, we'll discuss something that you probably are already familiar with;
The if function internally implements closures, transparently to you. Let's take a look at some code:
If you notice, this code will only message you "Hello World", it will not message the other code. However this will message you twice:
So, why is that? That's because the code in the if function is lazily evaluated. That is to say, it's not evaluated until it is determined that it is needed. Other functions do this too, some for optimization reasons, others, because it's just the nature of the function. For instance,
for() does as well. In most cases, the "special"
functions, (functions that have a language construct dedicated to them in other languages) implement closures. Some
functions, such as
and also are lazily evaluated, for performance reasons. Consider the case:
and(false, true, true, true, ....lots of trues). If we evaluate from left to right (which we do), after we
determine that the first argument evaluates to false, we know for a fact that the
and will return false,
regardless of what the rest of the arguments return. Same with
or and true values:
or(true, false, false, false).
However, in the case of the
array(), this has to be hastily evaluated, that is, we need to know how ALL
the arguments are going to resolve, before we send them to the array function itself. So if we break it down, with
if, we are sending code to the function, and with
array we are sending values to the
function. When we are sending code to a function, as an executable set of instructions, we say we have created a
When we think about the
proc function, we then realize that it's much like a
closure. When we define the procedure, nothing is run at that time. And in fact, internally, it does create a type of
closure. Procs are slightly special though, because they do not save environment information (at least variable scope).
Let's observe the more complicated case with an if statement:
This works, and messages "variable" like we would expect. However, the following does not work the same:
This will send us a blank message, because @a is not in scope inside of the proc. This makes it not a true closure, by the technical definition, but it's pretty close. The difference is that the environment is not stored along with the code. A mscript closure uses this concept, but also adds an extra feature, the ability to pass in extra environment information upon actual execution of the code.
The signature of the
closure function looks quite similar to
The vars passed in must be ivars, but much like procs, you may
assign default values to them.
Also, note that the code inside the closure has access to the special variable @arguments, which is an array containing all the arguments passed to the closure.
Values may also be returned from the closure, using
Execute is used as follows:
The values may be any value, but the closure must be a closure created with the closure function. A small, self contained example then:
Any values returned from the closure are returned by
The closure function actually returns a special data type, a closure. This data type has all the same qualities
of other data types, that is, it can be stored in a variable, passed around to procedures, returned, imported/exported,
persisted. It's very much like an anonymous procedure, and in fact has nearly the same signature as the
proc function, minus the name. That's because you essentially give it a name when you store it somewhere,
for instance in a variable.
We also have access to variables that are passed in, if we named them:
Also, we can implement vararg functionality with the special @arguments variable:
If you define more parameters than are passed in, they take the default values that are assigned.
So, you may ask yourself, what's the difference between this and a proc, other than the name is missing? A huge difference. With a proc, all variables go out of scope, other than what you pass in. With closures, everything that was in scope remains in scope. The only thing that may get overridden is if you named a variable that is passed in, and in addition, the meta scope values, such as the current player also remain in scope.
Recall the example above:
If we rewrite this with closures, we can see the difference:
In the first example with procs, we get an empty message. With the second example, we get msg'd "variable", because @a remained in scope when we executed the closure.
Now, it is important to note that the scope is frozen at closure bind time, NOT at execution time, so the following will happen:
This is because when we execute the closure, the values have already been bound, and can no longer be changed, as the closure sees it. This doesn't mean that something can't pass in extra values though, that's where the extra arguments come from. Let's modify the previous example some.
As you can see, we are able to change the environment at execution time if we put a little more thought into it.
Another important point to make here is to remind you that arrays are passed by reference, so the following is possible:
Assigning a variable inside of a closure will not affect the external variable table however, so this code will function as follows:
This is because arrays are passed by reference, not by value, and it's simply the reference that is getting copied into the closure's @array variable. So, if inside the closure, we change the values pointed at by the reference, those changes are visible from outside the closure. However, in the second example, we're changing the reference to the array, and simply putting something else inside of it. This doesn't affect the outside code having a reference to the array though, it still is pointing to the same empty array it created earlier.
An iclosure is the same thing as a closure, except the variable scope is different. The variable scope works like a procedure; no variables from the parent scope are retained. An iclosure with no arguments passed to it, then, has no variables in scope (other than @arguments). This is useful as an optimization technique, when you have a closure in a large program, it has to copy all the references of all the currently in scope variables. If all the values you need are going to be passed into the closure, there's no point in doing all that copying. Instead, you can just use an iclosure, thus possibly reducing your memory footprint. An iclosure is identical to closures in all other ways, and they can be used in any case that a closure is required.
Serialization of closures
Closures can be serialized too! They get output as a basic string, however, so it's important to note the advantages/disadvantages of this. When they get serialized, they completely loose their scope. They become a standalone string. If you output the string, it may not look precisely like it did when you put it in either, in fact it will have sconcats added, and be minified, however, the guarantee is that it is functionally equivalent. However, do note that if you import/export the closure, the scope will remain, it is only if you move the closure outside of CH that it will lose it's scope (for instance, with store_value/get_value). If the closure is to-string'd, you will have to use eval() on it, instead of execute, and if previously it had accepted values, you'll have to manually assign them yourself. They are not meant for out of memory storage, and so they do not support that very well, however, it is important to note what their behavior would be if you attempt to serialize them.
The most common usage that you will find is likely when you use a built in function that requires use of a closure. You may also use them in a library project, where you are wanting to create a callback of some sort, or as a way to abstract up a loop, for instance.