Blocks
We can embed a sequence of statements inside "try", called a "block". Defined variables are only visible within that block, not outside:
Using the "def" keyword is optional because we are inside a script:
But variables without "def" are visible outside the block:
We can't define a variable (using "def") with the same name as another already visible (ie, another "in scope"):
We can nest blocks:
Closures
We can take a sequence of statements that refers to its external context and assign it to a variable, then execute it later. It's technically called a "closable block", commonly called a "closure":
The closure assigned to the variable (here, c) will remember its context (here, including a) even if that context is not in scope when the closure is called:
A closure always returns a value, the result of its last statement:
By putting a closure within another, we can create two instances of it:
Closure Parameters
We can put parameters at the beginning of a closure definition, and pass values in when we call the closure:
We can also pass information out using the parameters:
One parameter is always available, called "it", if no explicit parameters are named:
If parameters aren't specified, "it" will still be implicitly defined, but be null:
Parameters can't have the same name as another variable in scope, except for the implicit parameter 'it':
If there's already a variable called 'it' in scope, we can access it using owner.it:
We can pass one closure into another as a parameter:
We can return a closure from another:
There's a shortcut syntax when explicitly defining a closure within another closure call, where that closure is the last or only parameter:
Arguments in a closure call can be named. They are interpreted as the keys in a map passed in as the first parameter:
We can enquire the number of parameters for a closure, both from inside and outside the closure:
A closure may have its last parameter/s assigned default value/s:
A closure can take a varying number of arguments by prefixing its last parameter with Object[], and accessing them using 'each':
We can also prefix the last parameter of a closure with Closure[] to pass in a varying number of other closures, even using the shortcut syntax:
When we call a closure with a list argument, if there's no closure defined with a list parameter, the arguments are passed in as separate parameters:
A closure may be copied with its first parameter/s fixed to a constant value/s, using curry:
In closures, we can use currying and parameter-count-varying together:
We can make closures recursive:
Functions
A function is similar to a closure, though a function can't access defined variables in its surrounding context:
The def keyword is compulsory when defining functions:
We use a special syntax to assign a function to another variable when using the original definition name:
Unlike blocks and closures, we can't nest functions:
Function Parameters
A function can have parameters, with which we can pass information both in and out:
We can have more than one function of the same name if they each have different numbers of (untyped) parameters.
A function returns a value, unless prefixed by void instead of def, when it always returns null:
When there's a method and closure with the same name and parameters, the method is chosen instead of the closure:
Some Similarities with Closures
We can use the shortcut invocation syntax for closure parameters:
A function may have its last parameter/s assigned default value/s:
Arguments in a function call can be named, interpreted as keys in a map passed in as first parameter:
A function can take a varying number of arguments by prefixing its last argument by Object[], and accessing them using each:
When we call a function with a list argument, if there's none defined with a list parameter, the arguments are passed in separately:
We can call a function recursively by referencing its own name: