[!NOTE] references:
A closure definition follows this syntax:
{ [closureParameters -> ] statements }
Closure clos = { println "Hello World" } assert clos.call() == clos() | + implicit call + explicit call
Closure VS. Method
closures have benefits over regular methods and are a powerful feature of Groovy:
- We can pass a Closure as an argument to a method
- Unary closures can use the implicit it parameter
- We can assign a Closure to a variable and execute it later, either as a method or with call
- Groovy determines the return type of the closures at runtime
- We can declare and invoke closures inside a closure
- Closures always return a value
method
def formatToLowerCase( String name ) { name.toLowerCase() }
closure
def formatToLowerCaseClosure = { name -> name.toLowerCase() }
break from closure
tips:
return
means continuereturn true
means break the loop
def list = [1, 2, 3, 4, 5]
list.any { element ->
if (element == 2)
return // continue
println element
if (element == 3)
return true // break
}
curry
left curry
def multiply = { x, y -> return x * y } def triple = multiply.curry(3) // triple = { y -> return 3 * y }
example
def nCopies = { int n, String... str -> str.join('')*n } def twice = nCopies.curry(2) println twice('|', '\\', '|', '/' ) assert twice('|', '\\', '|', '/' ) == nCopies( 2, '|', '\\', '|', '/' )
- result
|\|/|\|/
- result
others left curry
def multiConcat = { int n, String... args -> args.join('')*n } multiConcat( 3, '*', '-', '=' )
- result
*-=*-=*-=
- result
right curry
def nCopies = { int n, String str -> str*n } def twice = nCopies.rcurry( '*-=*=-*' ) println twice(2) assert twice(2) == nCopies( 2, '*-=*=-*' )
- result
*-=*=-**-=*=-*
[!TIP]
def nCopies = { int n, String str -> str*n } def twice = nCopies.rcurry( '*-=*=-*' ) def divider = nCopies.curry( 2 ) assert nCopies( 2, '*-=*=-*' ) == twice( 2 ) // right curry assert nCopies( 2, '-.-.-.-' ) == divider( '-.-.-.-' ) // left curry
- result
index with curry
def volume = { int l, int w, int h -> "l: ${l}\nw: ${w}\nh: ${h}" } def fixedWidthVolume = volume.ncurry(1, 2) println fixedWidthVolume( 3, 4 )
- result
l: 3 w: 2 h: 4
- result
Memoization
Fibonacci suite :
fib(15)
==fib(14)
+fib(13)
fib(14)
==fib(13)
+fib(12)
slow
def fib fib = { long n -> n<2 ? n : fib(n-1) + fib(n-2) } println fib(10) // result 55
fast
tips:
Closures.memoize()
def fib fib = { long n -> n<2 ? n : fib(n-1) + fib(n-2) }.memoize() fib(50) // result 12586269025
composition
double composition
def plus2 = { it + 2 }
def times3 = { it * 3 }
def times3plus2 = plus2 << times3
// | + execute first
// + execute second
// result
assert times3plus2(3) == 3*3+2
assert times3plus2(3) == plus2(times3(3))
def plus2times3 = plus2 >> times3
// | + execute last
// + execute first
// result
assert plus2times3(3) == (3+2)*3
assert plus2times3(3) == times3(plus2(3))
assert ( plus2 << times3 )(3) == ( times3 >> plus2 )(3) // | + execute first | + execute last // + execute last + execute first
-
def map1 = [ a: 10 , b: 2 , c: 3 ] def map2 = [ b: 3 , c: 2 , d: 5 ] def maps = [ map1 , map2 ] def process( def maps, Closure myLambda ) { maps.sum { it.keySet() }.collectEntries { key -> [ key, { x -> x.subList(1, x.size()).inject(x[0], myLambda) }( maps.findResults { it[key] } ) ] } } def sumResult = process(maps) { a, b -> a + b } def prodResult = process(maps) { a, b -> a * b } def minResult = process(maps) { a, b -> a < b ? a : b } assert sumResult == [ a:10, b:5, c:5, d:5 ] assert prodResult == [ a:10, b:6, c:6, d:5 ] assert minResult == [ a:10, b:2, c:2, d:5 ]
- resolution
assert [2,4,5].inject(1){ a, b -> a + b } == 12 assert [2,4,5].inject(1, { a, b -> a + b }) == 12
- resolution
triple composition
def multiply = { x, y -> return x * y }
def triple = multiply.curry(3)
def quadruple = multiply.curry(4)
def composition = { f, g, x -> return f(g(x)) }
def twelveTimes = composition.curry(triple, quadruple) // twelveTimes = { y -> composition { y -> 3*(4*y) } }
def threeDozen = twelveTimes(3)
methods
various method to call closure
def work( String input, Closure cl ) { cl(input) } Closure assertJava = { it == 'Java' } println work( "Java", assertJava ) println work("Java", { it == 'Java' }) // ==> work 'Java', { it == 'Java' } println work( 'Java' ){ it == 'Java' }
frequent usage
def on( String name, String dString = 'is' ) { [ skip: { -> println "no params. skip" }, foo: { String f, Map fmap -> foo( name, dString, f, fmap ) [ bar: { Map map -> bar( '', map ) } ] } , bar: { Map map -> bar( name, map ) } ] } def reset( Map m ) { m.collect{ k, v -> "${k} : ${v}" }.join('\n') } def foo( String n, String ds, String f, Map m ) { println "${n} ${ds} ${f} !\ndetails :\n\t\t${reset(m)}" } def bar( String n = '', Map m ) { println "${n ? "${n}\n": ''}\t\t${reset(m)}" }
result
on('marslo') .foo( 'awesome', [ 'age' : 34 ] ) .bar( ['gender' : 'female' ] ) // result marslo is awesome ! details : age : 34 gender : female
or
on('marslo').skip() // result no params. skip
delegate
[!NOTE] references:
List.metaClass.collectWithIndex = { yield ->
def collected = []
delegate.eachWithIndex { listItem, index ->
collected << yield(listItem, index)
}
return collected
}
result = list.collectWithIndex { it, index -> "${index + 1}. ${it}" }
-
List.metaClass.collectWithIndex = { body-> def i=0 delegate.collect { body(it, i++) } } // or even List.metaClass.collectWithIndex = { body-> [ delegate, 0..<delegate.size() ].transpose().collect(body) }
tricky
this
class Enclosing {
void run() {
def whatIsThisObject = { getThisObject() }
assert whatIsThisObject() == this
def whatIsThis = { this }
assert whatIsThis() == this
}
}
Enclosing e = new Enclosing()
e.run()
using Map to define the actions
Map actions = [
'a': [ 'foo', 'bar' ],
'b': [ 'foo' ],
'c': [ 'bar' ]
]
def actionFunc() {
Closure aFoo = { -> println ".. foo" }
Closure aBar = { -> println ".. bar" }
[
'foo': { aFoo() },
'bar': { aBar() }
]
}
// execute actions with associated closure
actions.each {
println ">> ${it.key}:"
it.value.each { v -> actionFunc()["${v}"].call() }
}
results:
>> a: .. foo .. bar >> b: .. foo >> c: .. bar
same as using switch-case:
actions.each { println ">> ${it.key}:" it.value.each { v -> switch ( v ) { case "foo": actionFunc().foo() break case "bar": actionFunc().bar() break } } }
example
Closure return Closure
simple
def withClosure( Object object, Closure body ) { Closure bar = { obj -> obj.toString() } Closure foo = { cid -> [ (java.lang.String) : { body([ claz: cid.class.simpleName, str: bar(cid) ]) } , (java.util.ArrayList) : { body([ claz: cid.class.simpleName, list: bar(cid) ]) } ].find { it.key.isAssignableFrom( cid.getClass() ) }.value.call() } foo object } withClosure('s') { b -> println "${b.claz} -> ${b.str}" } withClosure(['a']) { b -> println "${b.claz} -> ${b.list}" } -- result -- String -> s ArrayList -> [a]
working with Jenkins Credential Class
import com.datapipe.jenkins.vault.credentials.common.VaultSSHUserPrivateKeyImpl import com.datapipe.jenkins.vault.credentials.common.VaultUsernamePasswordCredentialImpl import com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl import com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey import com.cloudbees.plugins.credentials.CredentialsProvider import com.cloudbees.plugins.credentials.common.StandardCredentials import groovy.transform.Field def withCredential( String credentialsId, Closure body ) { Closure foo = { cid -> [ ( VaultSSHUserPrivateKeyImpl ) : { body([ username: cid.username, id: cid.id ]) }, ( VaultUsernamePasswordCredentialImpl ) : { body([ username: cid.username, id: cid.id ]) }, ( UsernamePasswordCredentialsImpl ) : { body([ username: cid.username, id: cid.id ]) }, ( BasicSSHUserPrivateKey ) : { body([ username: cid.username, id: cid.id ]) } ].find { it.key.isAssignableFrom( cid.getClass() ) }.value.call() } foo CredentialsProvider.lookupCredentials( StandardCredentials.class, jenkins.model.Jenkins.instance).find { credentialsId == it.id } } withCredential( 'BASIC_CREDENTIAL' ) { account -> println "${account.id.getClass()} -> ${account.username}" } withCredential( 'SSH_CREDENTIAL' ) { account -> println "${account.id.getClass()} -> ${account.username}" } -- result -- [Pipeline] Start of Pipeline [Pipeline] echo class java.lang.String -> functional-account [Pipeline] echo class java.lang.String -> functional-account [Pipeline] End of Pipeline Finished: SUCCESS
Closure return Closure with DSL
def withCredential( String credentialsId, Closure body ) { Closure foo = { cid -> [ ( VaultSSHUserPrivateKeyImpl ) : { withCredentials([[ $class: 'VaultSSHUserPrivateKeyBinding', credentialsId: credentialsId, privateKeyVariable : 'SSHKEY', usernameVariable: 'USERNAME' ]]) { body([ username : '${USERNAME}', sshkey : '${SSHKEY}' ]) } }, ( VaultUsernamePasswordCredentialImpl ) : { withCredentials([[ $class: 'VaultUsernamePasswordCredentialBinding', credentialsId: credentialsId, passwordVariable: 'PASSWORD', usernameVariable: 'USERNAME' ]]) { body([ username : '${USERNAME}', password : '${PASSWORD}' ]) } }, ( UsernamePasswordCredentialsImpl ) : { withCredentials([ usernamePassword( credentialsId: credentialsId, usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD' ) ]) { body([ username : '${USERNAME}', password : '${PASSWORD}' ]) } }, ( BasicSSHUserPrivateKey ) : { withCredentials([ sshUserPrivateKey( credentialsId: credentialsId, keyFileVariable: 'SSHKEY', usernameVariable: 'USERNAME' ) ]) { body([ username : '${USERNAME}', sshkey : '${SSHKEY}' ]) } } ].find { it.key.isAssignableFrom( cid.getClass() ) }.value.call() } foo CredentialsProvider.lookupCredentials( StandardCredentials.class, jenkins.model.Jenkins.instance).find { credentialsId == it.id } } def download( String url, String credential = 'BASIC_CREDENTIAL' ) { withCredential( credential ) { account -> sh """ set +x [ ! -z '${name}' ] && [ ! -d \$(dirname '${name}') ] && mkdir -p \$(dirname '${name}') bash -c "/usr/bin/curl -u${account.username}:${account.password} -skg ${url}" """ } // withCredential }