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
}
}
}
|
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
|
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
|
We can even make a recursion of anonymous closures (thanks to 'call' method available for each closure)
def results = [];
{ a, b ->
results << a
a<10 && call(b, a+b)
}(1,1)
assert results == [1, 1, 2, 3, 5, 8, 13] // Fibonacci numbers
|
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
}
|
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'
|
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
|