-
Notifications
You must be signed in to change notification settings - Fork 0
Language Concepts: Closures
Closures are special language constructs that can be used to define quick in-lined callable function that capture the context of the function they are being executed on. Closures, like functions, can be passed around as a value and have a callable type signature.
In short, a closure functions exactly like a function, with the added bonus of capturing the local variables of the function it was created in.
Closure sample:
// Function that returns a callable closure that multiplies values provided by a set amount
func createMultiplier(amount:int) : (int->int) // Define a callable return type
{
return (i:int) : int => { return i * amount; };
}
let mult5 = createMultiplier(5);
// Multiply some values
print(mult5(2));
print(mult5(3));
print(mult5(4));Prints
10
15
20
Closures are defined by the following syntax:
( <parameter list> ) : <return type> => { statements }
The part containing the parameter list and return type have the same semantics of a normal function definition, meaning you can define multiple parameters, optional parameters and variadic parameters:
// Parameterless closure
() : void => { print("I'm empty!"); }();
// Multiple-parametered closure
(n1:int, n2:int) : void => { print(n1 + n2); }(1, 2);
// Default parametered closure. Notice how the type of 'i' was omitted: it was inferred the type of 'int' because of its default value
(i = 0) : void => { print("Value: " + i); }();
// Variadic-parametered closure
(items:int...) : void => {
print("List size: " + items.Count);
for(var i:int = 0; i < items.Count; i++)
{
print("Item " + (i + 1) + ": " + items[i]);
}
}(0, 1);Note: You can call a closure immediately after defining it by appending a pair of parenthesis with matching arguments after its closing curly bracket (})
If the closure has a single parameter, the parenthesis around the parameter can be omitted:
var printUpper = str:string => { print(str.ToUpperCase()); };Note: The curly brackets enclosing the body are always required
Closures, having a callable type signature, can specify a return value on its definition. This is done with the : <type> bit, which is passed after the parameter list:
() : int => { return 0; };Note: Just like functions, closures have their return type checked, and the same errors that apply to a function in that context, like returning values on a void return type, apply to a closure.
And just like in a function, the return type can be omitted, assuming a void closure:
// Spooky empty closure; What could have caused the rushed departure of the statements that once lived within? Maybe we will never know... *spooky lightnings*
() => { }Closures are special in relation to normal functions because they can capture variables and use them as if they where local variables of the closure itself. This allows closures to maintain the state they where last executed on intact.
An example of closure capturing was displayed on the first sample in this file, but lets revisit it and inspect what is going on:
func createMultiplier(amount:int) : (int->int)
{
return (i:int) : int => { return i * amount; };
}Notice the returned closure: it references the amount variable, which was specified in a function parameter outside!
The variables captured are specific for each time the function containing the closure is called, that is, by creating multiple closures with multiple calls of createMultiplier(), each closure will reference a different amount value:
...
let mult5 = createMultiplier(5);
let mult7 = createMultiplier(5);
print(mult5(2));
print(mult7(2));Prints
10
14Note that the captured variables inside closures are not copies of the original variables, but the same variables as in the outer scopes. That means that if a closure modifies a captured variables inside, it modifies the original variables as well:
var i = 0;
let increment = () => { i++; }; // Increments the 'i' variable by one each time this closure is called
print(i);
increment();
print(i);Prints
0
1
Closures can be nested inside other closures, capturing variables in a nested manner, and they all ultimately capture the running function's locals.
This can allow for some interesting behaviors:
let createMultiplier = (operand1:int):(int->int) => {
return (operand2:int):int => {
return operand1 * operand2;
};
};
let multBy2 = createMultiplier(2);
let multBy3 = createMultiplier(3);
print(multBy2(3));
print(multBy3(3));Prints:
6
9
When writing closure parameters and return types, a type must be specified, but some conditions present the opportunity to allow the compiler to decide what those types will be, and infer the whole closure signature, including parameter and return types.
Type inference of closures occur in the given scenarios:
func printThat(c:(->int))
{
print(c());
}
printThat(() => { return 10; });Prints
10
In the above example, the return type of the closure is omitted, but since it is being passed to a typed function argument, it infers the type (->int) from the argument's signature, allowing it to return a value.
The above is also possible with closure arguments:
func callWithTen(c:(int->))
{
c(10);
}
callWithTen(i => { print(i + 10); });Prints
20
In this case, the i parameter from the closure was inferred the type int from the parameter's signature.
In return values, the same type inferring mechanics apply:
func generator(start:int = 0) : (->int)
{
return () => { return start++; };
}
let g = generator(1);
print(g());
print(g());
print(g());Prints
0
1
2
The closure had no explicit return type, and since it was omitted, it was automatically inferred as int from the function's return type (->int).
Closure signatures can be inferred when creating local and global variables, like in the in-lined implementation of the function in the section above:
var start = 0;
let g:(->int) = () => { return start++; };
print(g());
print(g());
print(g());Prints
0
1
2
And lastly, closures can also be inferred when assigned to a value holder that has a signature of callable type:
var start = 0;
var g:(->int)?;
g = () => { return start++; };
print(g?());
print(g?());
print(g?());Prints
0
1
2
To prevent types of the closure signature from being inferred, simply declare them explicitly:
let g2:(->int) = () : void => { print("Hey there!"); };The above code raises an error, because since the : void return type was made explicit, the compiler was not able to infer it to int, like the value holder expected, creating a mismatched type error.