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'
assert a == 'good morning'
assert b == 'greetings'
}
assert a == 'good morning'
Using the "def" keyword is optional because we are inside a script:
def c = 5
assert c == 5
d = 6
assert d == 6 assert binding.variables.c == null
assert binding.variables.d == 6
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"):
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 }
assert c() == 'coffee and tea'
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 } }
assert c() == 'sugar'
def d = c assert d() == 'sugar'
A closure always returns a value, the result of its last statement:
giveSeven = { 7 }
assert giveSeven() == 7
giveNull = { def a }
assert giveNull() == null
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,) 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 = { value1 -> def it = 789; [value1, it] }
assert c( 456 ) == [456, 789]
c = {-> def it = 789; it } assert c() == 789
Parameters can't have the same name as another variable in scope, except for the implicit parameter 'it':
def name= 'cup'
c= { def d= { 2 * it }; 3 * d(it) }
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 assert runTwice( 5 ){it * 3} == 45
def runTwiceAndConcat = { c -> c() + c() }
assert runTwiceAndConcat( { 'plate' } ) == 'plateplate' assert runTwiceAndConcat(){ 'bowl' } == 'bowlbowl' assert runTwiceAndConcat{ 'mug' } == 'mugmug'
def runTwoClosures = { a, c1, c2 -> c1(c2(a)) }
assert runTwoClosures( 5, {it*3}, {it*4} ) == 60 assert runTwoClosures( 5 ){it*3}{it*4} == 60
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'
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) }
}
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 ) assert d( 2, 3, 4 ) == '1, 2, 3, 4'
def e = c.curry( 1, 3 ) assert e( 5 ) == '1, 3, 5'
def f = e.curry( 5, 7, 9, 11 ) assert f( 13, 15 ) == '1, 3, 5, 7, 9, 11, 13, 15'
We can make closures recursive:
def gcd 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 c = 'there', d = 'yonder'
def f(){
assert a == 32 def c = 'here'
c
}
assert f() == 'here'
The def keyword is compulsory when defining functions:
def f(){
a = 1
c = { 'here, again' }
c()
}
assert f() == 'here, again'
We use a special syntax to assign a function to another variable when using the original definition name:
def f(){ 77 } assert f() == 77
def g = this.&f assert g() == 77
def h = g assert h() == 77
f = 'something else' assert f() == 77
Unlike blocks and closures, we can't nest functions:
def f(){
'here'
}
assert f() == 'here'
try{
}
c = {
}
def h(){
try{ def c = { 'here, again' } }
}
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
def f2(){ return 8; 3 }
assert f2() == 8
void f3(){ 10 }
assert f3() == null
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