Blocks
We can embed a sequence of statements inside "try", called a "block". Defined variables are only visible within that block, not outside:
def a = 'good morning' try{ def b = 'greetings', c = 'nice day' //'def' keyword applies to both 'b' and 'c' assert a == 'good morning' assert b == 'greetings' } assert a == 'good morning' //println b //a compile error if uncommented: b not visible here
Using the "def" keyword is optional because we are inside a script:
def c = 5 assert c == 5 d = 6 assert d == 6 //def keyword optional because we're within a script context assert binding.variables.c == null assert binding.variables.d == 6 //when def not used, variable becomes part of binding.variables
But variables without "def" are visible outside the block:
try{ h = 9 assert binding.variables.h == 9 } assert h == 9 assert binding.variables.h == 9
We can't define a variable (using "def") with the same name as another already visible (ie, another "in scope"):
def a = 'island' //def a = 'snake' //a compile error if uncommented: a already defined try{ //def a = 'jewel' //a compile error if uncommented: a already defined }
We can nest blocks:
def a = 123 try{ try{ try{ assert a == 123 } } }
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":
def a = 'coffee'
def c = {
def b = 'tea'
a + ' and ' + b //a refers to the variable a outside the closure,
//and is remembered by the closure
}
assert c() == 'coffee and tea' //short for c.call()
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:
def c try{ def a = 'sugar' c = { a } //a closure always returns its only value } assert c() == 'sugar' def d = c //we can also assign the closure to another variable assert d() == 'sugar'
A closure always returns a value, the result of its last statement:
giveSeven = { 7 }
assert giveSeven() == 7 //value of last statement is returned
giveNull = { def a }
assert giveNull() == null //null returned if last statement has no value
By putting a closure within another, we can create two instances of it:
c = { def e = { 'milk' }; e }
d = c
assert c == d
v1 = c()
v2 = c()
assert v1 != v2
Closure Parameters
We can put parameters at the beginning of a closure definition, and pass values in when we call the closure:
def toTriple = {n -> n * 3}
assert toTriple.call( 5 ) == 15
We can also pass information out using the parameters:
def f = { list, value -> list << value }
x = []
f(x, 1)
f(x, 2,) //trailing comma in argument list OK
f(x, 3)
assert x == [1, 2, 3]
One parameter is always available, called "it", if no explicit parameters are named:
c = { it*3 }
assert c( 'run' ) == 'runrunrun'
If parameters aren't specified, "it" will still be implicitly defined, but be null:
//c = { def it = 789 } //a compile error when uncommented: 'it' already implicitly defined c = { value1 -> def it = 789; [value1, it] } //works OK because no 'it' among parameters assert c( 456 ) == [456, 789] c = {-> def it = 789; it } //zero parameters, not even 'it', so works OK assert c() == 789
Parameters can't have the same name as another variable in scope, except for the implicit parameter 'it':
def name= 'cup' //def c={ name-> println (name) } //a compile error when uncommented: //current scope already contains name 'name' c= { def d= { 2 * it }; 3 * d(it) } //'it' refers to immediately-surrounding closure's parameter in each case assert c(5) == 30
If there's already a variable called 'it' in scope, we can access it using owner.it:
it= 2
c= { assert it == 3; assert owner.it == 2 }
c(3)
We can pass one closure into another as a parameter:
toTriple = {n -> n * 3}
runTwice = { a, c -> c( c(a) )}
assert runTwice( 5, toTriple ) == 45
We can return a closure from another:
def times= { x -> { y -> x * y }}
assert times(3)(4) == 12
There's a shortcut syntax when explicitly defining a closure within another closure call, where that closure is the last or only parameter:
def runTwice = { a, c -> c(c(a)) }
assert runTwice( 5, {it * 3} ) == 45 //usual syntax
assert runTwice( 5 ){it * 3} == 45
//when closure is last param, can put it after the param list
def runTwiceAndConcat = { c -> c() + c() }
assert runTwiceAndConcat( { 'plate' } ) == 'plateplate' //usual syntax
assert runTwiceAndConcat(){ 'bowl' } == 'bowlbowl' //shortcut form
assert runTwiceAndConcat{ 'mug' } == 'mugmug'
//can skip parens altogether if closure is only param
def runTwoClosures = { a, c1, c2 -> c1(c2(a)) }
//when more than one closure as last params
assert runTwoClosures( 5, {it*3}, {it*4} ) == 60 //usual syntax
assert runTwoClosures( 5 ){it*3}{it*4} == 60 //shortcut form
Arguments in a closure call can be named. They are interpreted as the keys in a map passed in as the first parameter:
def f= {m, i, j-> i + j + m.x + m.y }
assert f(6, x:4, y:3, 7) == 20
def g= {m, i, j, k, c-> c(i + j + k, m.x + m.y) }
assert g(y:5, 1, 2, x:6, 3){a,b-> a * b } == 66
We can enquire the number of parameters for a closure, both from inside and outside the closure:
c= {x,y,z-> getMaximumNumberOfParameters() }
assert c.getMaximumNumberOfParameters() == 3
assert c(4,5,6) == 3
A closure may have its last parameter/s assigned default value/s:
def e = { a, b, c=3, d='a' -> "${a+b+c}$d" }
assert e( 7, 4 ) == '14a'
assert e( 9, 8, 7 ) == '24a' //override default value of 'c'
A closure can take a varying number of arguments by prefixing its last parameter with Object[], and accessing them using 'each':
def c = { arg, Object[] extras ->
def list= []
list<< arg
extras.each{ list<< it }
list
}
assert c( 1 ) == [ 1 ]
assert c( 1, 2 ) == [ 1, 2 ]
assert c( 1, 2, 3 ) == [ 1, 2, 3 ]
assert c( 1, 2, 3, 4 ) == [ 1, 2, 3, 4 ]
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:
def apply = { a, Closure[] cc ->
(cc as List).inject(a){ flo, it-> it(flo) }
//apply the closures nestedly to the initial value
}
assert apply(7){it*3}{it+1}{it*2}.toString() == '44'
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:
def c= {a, b, c-> a + b + c}
def list=[1,2,3]
assert c(list) == 6
A closure may be copied with its first parameter/s fixed to a constant value/s, using curry:
def concat = { p1, p2, p3 -> "$p1 $p2 $p3" }
def concatAfterFly = concat.curry( 'fly' )
assert concatAfterFly( 'drive', 'cycle' ) == 'fly drive cycle'
def concatAfterFlySwim = concatAfterFly.curry( 'swim' )
assert concatAfterFlySwim( 'walk' ) == 'fly swim walk'
In closures, we can use currying and parameter-count-varying together:
def c = { arg, Object[] extras -> arg + ', ' + extras.join(', ') }
def d = c.curry( 1 ) //curry first param only
assert d( 2, 3, 4 ) == '1, 2, 3, 4'
def e = c.curry( 1, 3 ) //curry part of Object[] also
assert e( 5 ) == '1, 3, 5'
def f = e.curry( 5, 7, 9, 11 ) //currying continues on Object
assert f( 13, 15 ) == '1, 3, 5, 7, 9, 11, 13, 15'
We can make closures recursive:
def gcd //predefine closure name gcd={ m,n-> m%n==0? n: gcd(n,m%n) } assert gcd( 28, 35 ) == 7
Functions
A function is similar to a closure, though a function can't access defined variables in its surrounding context:
a = 32 //def keyword not used for this one def c = 'there', d = 'yonder' def f(){ assert a == 32 //outer 'a' visible because 'def' keyword wasn't used with it def c = 'here' //compiles OK because other defined c invisible inside function definition //println d //a compile error when uncommented: d not accessable c } assert f() == 'here' //syntax to invoke a function
The def keyword is compulsory when defining functions:
def f(){
a = 1
c = { 'here, again' }
c()
}
assert f() == 'here, again'
//g(){ println 'there, again' }
//a compile error when uncommented: def keyword required
We use a special syntax to assign a function to another variable when using the original definition name:
def f(){ 77 } //define function using name 'f'
assert f() == 77
def g = this.&f //special syntax to assign function to another variable
assert g() == 77
def h = g //don't use special syntax here
assert h() == 77
f = 'something else' //this 'f' is a VARIABLE, not the function NAME
assert f() == 77 //the function name can't be reassigned
Unlike blocks and closures, we can't nest functions:
def f(){
//def g1(){ println 'there' }
//a compile error when uncommented: can't nest functions
'here'
}
assert f() == 'here'
try{
//def g2(){ println 'yonder' }
//a compile error when uncommented: can't nest functions
}
c = {
//def g3(){ println 'outer space' }
//a compile error when uncommented: can't nest functions
}
def h(){
try{ def c = { 'here, again' } }
//we can have blocks and closures within functions
}
Function Parameters
A function can have parameters, with which we can pass information both in and out:
def foo( list, value ){
list << value
}
x = []
foo(x, 1)
foo(x, 2)
assert x == [1, 2]
We can have more than one function of the same name if they each have different numbers of (untyped) parameters.
def foo(value){ 'v1' }
def foo(list, value){ 'v2' }
assert foo(9) == 'v1'
assert foo([], 1) == 'v2'
A function returns a value, unless prefixed by void instead of def, when it always returns null:
def f1(){ 7 }
assert f1() == 7 //value of last statement is returned
def f2(){ return 8; 3 }
assert f2() == 8 //return explicitly using return
void f3(){ 10 }
assert f3() == null //null always returned
//void f4(){ return 9 }
//a compile error when uncommented: can't use 'return' in a void function
When there's a method and closure with the same name and parameters, the method is chosen instead of the closure:
def c(){'method c'}
def c= {-> 'closure c'}
assert c() == 'method c'
def d(i){'method d'}
def d= {'closure d'}
assert d(9) == 'method d'
Some Similarities with Closures
We can use the shortcut invocation syntax for closure parameters:
def f(Closure c){ c() }
assert f{ 'heehee' } == 'heehee'
A function may have its last parameter/s assigned default value/s:
def dd( a, b=2 ){ "$a, $b" }
assert dd( 7, 4 ) == '7, 4'
assert dd( 9 ) == '9, 2'
Arguments in a function call can be named, interpreted as keys in a map passed in as first parameter:
def f(m, i, j){ i + j + m.x + m.y }
assert f(6, x:4, y:3, 7) == 20
def g(m, i, j, k, c){ c(i + j + k, m.x + m.y) }
assert g(y:5, 1, 2, x:6, 3){a,b-> a * b } == 66
A function can take a varying number of arguments by prefixing its last argument by Object[], and accessing them using each:
def c( arg, Object[] extras ){ def list= [] list<< arg extras.each{ list<< it } list } assert c( 1 ) == [ 1 ] assert c( 1, 2, 3, 4 ) == [ 1, 2, 3, 4 ]
When we call a function with a list argument, if there's none defined with a list parameter, the arguments are passed in separately:
def x(a, b, c){a + b + c}
def list=[1,2,3]
assert x(list) == 6
We can call a function recursively by referencing its own name:
def gcd( m, n ){ if( m%n == 0 )return n; gcd(n,m%n) }
assert gcd( 28, 35 ) == 7






