You are viewing an older version of the site. Click here to view
the latest version of this page. (This may be a dead link, if so, try the root page of the docs
here.)
A closure is a block of code that is treated as ''code'', not as a ''result'' of code, which can then be
stored in a variable or passed as an argument, and then executed later, or in other code.
==Explanation==
So, what is a closure? You can [http://en.wikipedia.org/wiki/Closure_%28computer_science%29 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
Copy 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:
Copy Code
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,
Copy Code
This works, and messages "variable" like we would expect. However, the following does not work the same:
Copy Code
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. MethodScript's closure uses this concept, but also adds an extra feature, the ability to pass in extra
environment information upon actual execution of the code.
== Using closures ==
Using the {{function|closure}} and {{function|execute}} functions (or parenthesis based execution, discussed below)
allows you to harness the power of closures yourself, however you wish.
===closure()===
The signature of the
Copy Code
The vars passed in must be ivars, but much like procs, you may
Copy Code
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 {{function|return}}:
Copy Code
===execute()===
Execute is used as follows:
Copy Code
Any values returned from the closure are returned by
Copy Code
While the execute function is the explicit way of executing closures, they can also be executed through parenthesis
based execution, which perhaps looks more familiar and obvious.
Copy Code
=== Combined Usage ===
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
Copy Code
We also have access to variables that are passed in, if we named them:
Copy Code
{{TakeNote|text=@arguments support should not be heavily relied on in general. When typechecking is fully
introduced, and strict mode is turned on, this will begin to potentially cause compile errors. Alternative
syntax will be introduced to fully support variadic arguments in a typesafe way. Instead, in the meantime,
prefer passing in arrays.}}
Also, we can implement vararg functionality with the special @arguments variable:
Copy Code
If you define more parameters than are passed in, they take the default values that are assigned.
Copy Code
=== Scope ===
The largest difference between this and a proc, other than the name is missing, is that 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:
Copy Code
If we rewrite this with closures, we can see the difference:
Copy Code
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:
Copy Code
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.
Copy Code
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 and other objects that are not instanceof ValueType
are passed by reference, so the following is possible:
Copy Code
Assigning a variable inside of a closure will not affect the external variable table however, so this code will function
as follows:
Copy Code
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.
== iclosures ==
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.
== Common usage ==
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.
{{LearningTrail}}
if()
function.
The if function internally implements closures, transparently to you. Let's take a look at some code:
if(true){
msg('Hello World!');
} else {
msg('You should not see me!');
}

1 {{keyword|if}}({{keyword|true}}){
2 {{function|msg}}('Hello World!');
3 } {{keyword|else}} {
4 {{function|msg}}('You should not see me!');
5 }
array(msg('One message'), msg('Two message'));

1 {{function|array}}({{function|msg}}('One message'), {{function|msg}}('Two message'));
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 and design 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
'''closure'''.
When we think about the [[Procedures|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 different though, because they are named, and also do not save environment information
(at least variable scope). Let's observe the more complicated case with an if statement:
string @a = 'variable';
if(true){
msg(@a);
}

1 {{object|string}} @a = 'variable';
2 {{keyword|if}}({{keyword|true}}){
3 {{function|msg}}(@a);
4 }
string @a = 'variable';
proc _doit(){
msg(@a);
}
_doit();

1 {{object|string}} @a = 'variable';
2 {{keyword|proc}} {{function|_doit}}(){
3 {{function|msg}}(@a);
4 }
5 {{function|_doit}}();
closure
function looks quite similar to proc
.
[type] closure([@vars...,]) {
// Some code
};

1 [type] {{keyword|closure}}([@vars...,]) {
2 // Some code
3
4 };
assign
default values to them.
void closure(@a = 'a value', @b){
msg(@a @b)
};

1 {{object|void}} {{keyword|closure}}(@a = 'a value', @b){
2 {{function|msg}}(@a @b)
3 };
int closure(){
return 5;
};

1 {{object|int}} {{keyword|closure}}(){
2 {{keyword|return}} 5;
3 };
(The following code doesn't compile)
execute([values...,] <Callable>);The values may be any value, but the closure must be a closure created with the closure function. A small, self contained example then:
execute(1, 2, 3, void closure(int @a, int @b, int @c){
msg(@a . @b . @c);
});

1 {{function|execute}}(1, 2, 3, {{object|void}} {{keyword|closure}}({{object|int}} @a, {{object|int}} @b, {{object|int}} @c){
2 {{function|msg}}(@a . @b . @c);
3 });
execute
.
int @five = execute(closure(){
return 5;
});
msg(@five); // 5

1 {{object|int}} @five = {{function|execute}}({{keyword|closure}}(){
2 {{keyword|return}} 5;
3 });
4 {{function|msg}}(@five); // 5
closure @c = int closure(){
return 10;
};
int @ten = @c();
msg(@ten); // 10
closure @r = void closure(int @a, int @b, int @c) {
msg("a: @a, b: @b, c: @c");
};
@r(1, 2, 3); // outputs "a: 1, b: 2, c: 3"

01 {{keyword|closure}} @c = {{object|int}} {{keyword|closure}}(){
02 {{keyword|return}} 10;
03 };
04
05 {{object|int}} @ten = @c();
06
07 {{function|msg}}(@ten); // 10
08
09
10 {{keyword|closure}} @r = {{object|void}} {{keyword|closure}}({{object|int}} @a, {{object|int}} @b, {{object|int}} @c) {
11 {{function|msg}}("a: @a, b: @b, c: @c");
12 };
13
14 @r(1, 2, 3); // outputs "a: 1, b: 2, c: 3"
proc
function, minus the name. That's because you essentially give it a name when you store it somewhere,
for instance in a variable.
closure @a = void closure(){
msg('This is some code that will run later');
};
// Now we have defined this closure in the variable @a
@a(); // Here, we actually execute the closure, we now get msg'd!

1 {{keyword|closure}} @a = {{object|void}} {{keyword|closure}}(){
2 {{function|msg}}('This is some code that will run later');
3 };
4 // Now we have defined this closure in the variable @a
5
6 @a(); // Here, we actually execute the closure, we now get msg'd!
closure @a = void closure(@one, @two){
msg("@one @two");
};
@a('one', 'two'); // msg's "one two"

1 {{keyword|closure}} @a = {{object|void}} {{keyword|closure}}(@one, @two){
2 {{function|msg}}("@one @two");
3 };
4 @a('one', 'two'); // msg's "one two"
closure @a = void closure(){
msg('You passed in ' . array_size(@arguments) . ' arguments');
};
@a(1, 2, 3, 4, 5); //msg's "You passed in 5 arguments"

1 {{keyword|closure}} @a = {{object|void}} {{keyword|closure}}(){
2 {{function|msg}}('You passed in ' . {{function|array_size}}(@arguments) . ' arguments');
3 };
4 @a(1, 2, 3, 4, 5); //msg's "You passed in 5 arguments"
closure @a = void closure(string @one, string @two = 'two'){
msg(@one . ' ' . @two);
};
@a('one'); // msg's "one two"

1 {{keyword|closure}} @a = {{object|void}} {{keyword|closure}}({{object|string}} @one, {{object|string}} @two = 'two'){
2 {{function|msg}}(@one . ' ' . @two);
3 };
4 @a('one'); // msg's "one two"
string @a = 'variable';
void proc _doit(){
msg(@a);
}
_doit();

1 {{object|string}} @a = 'variable';
2 {{object|void}} {{keyword|proc}} {{function|_doit}}(){
3 {{function|msg}}(@a);
4 }
5 {{function|_doit}}();
string @a = 'variable';
closure @doit = void closure(){
msg(@a);
};
@doit();

1 {{object|string}} @a = 'variable';
2 {{keyword|closure}} @doit = {{object|void}} {{keyword|closure}}(){
3 {{function|msg}}(@a);
4 };
5 @doit();
string @value = 'Hello World!';
closure @closure = void closure(){
msg(@value);
};
// Let's change the value stored in @value
string @value = 'Goodbye World!';
// Now we execute the closure, which echoes what it thinks @value is
@closure(); // msg's "Hello World", not "Goodbye World"

1 {{object|string}} @value = 'Hello World!';
2 {{keyword|closure}} @closure = {{object|void}} {{keyword|closure}}(){
3 {{function|msg}}(@value);
4 };
5 // Let's change the value stored in @value
6
7 {{object|string}} @value = 'Goodbye World!';
8 // Now we execute the closure, which echoes what it thinks @value is
9
10 @closure(); // msg's "Hello World", not "Goodbye World"
string @value = 'Hello World!';
closure @closure = void closure(string @value){
msg(@value);
};
// Let's change @value again
@value = 'Goodbye World!';
// Now pass @value back in when we execute the closure
@closure(@value); // This time it does echo "Goodbye World"

1 {{object|string}} @value = 'Hello World!';
2 {{keyword|closure}} @closure = {{object|void}} {{keyword|closure}}({{object|string}} @value){
3 {{function|msg}}(@value);
4 };
5 // Let's change @value again
6
7 @value = 'Goodbye World!';
8 // Now pass @value back in when we execute the closure
9
10 @closure(@value); // This time it does echo "Goodbye World"
closure @a = void closure(@array){
@array[0] = 'Hello World';
};
array @value = array();
@a(@value);
msg(@value); // msg's "{Hello World}", because the array can be edited from inside the closure

1 {{keyword|closure}} @a = {{object|void}} {{keyword|closure}}(@array){
2 @array[0] = 'Hello World';
3 };
4 {{object|array}} @value = {{function|array}}();
5 @a(@value);
6 {{function|msg}}(@value); // msg's "{Hello World}", because the array can be edited from inside the closure
closure @a = void closure(@array){
@array = null;
};
array @value = array();
@a(@value);
msg(@value); // msg's array; it's still an array!

1 {{keyword|closure}} @a = {{object|void}} {{keyword|closure}}(@array){
2 @array = {{keyword|null}};
3 };
4 {{object|array}} @value = {{function|array}}();
5 @a(@value);
6 {{function|msg}}(@value); // msg's array; it's still an array!
Find a bug in this page? Edit this page yourself, then submit a pull request.