Groovy/FAQ/Подпрограммы

Материал из Wiki.crossplatform.ru

Перейти к: навигация, поиск
Groovy ·

Содержание

[править] Introduction

//----------------------------------------------------------------------------------
def hello() {
    greeted += 1
 
    println "hi there!"
}
 
// We need to initialize greeted before it can be used, because "+=" assumes predefinition
greeted = 0
hello()
println greeted
// =>
 
// hi there
// 1
//----------------------------------------------------------------------------------

[править] Accessing Subroutine Arguments

//----------------------------------------------------------------------------------
// basic method calling examples
// In Groovy, parameters are named anyway
def hypotenuse(side1, side2) {
    Math.sqrt(side1**2 + side2**2)    // sqrt in Math package
 
}
diag = hypotenuse(3, 4)
assert diag == 5
 
// the star operator will magically convert an Array into a "tuple"
a = [5, 12]
 
assert hypotenuse(*a) == 13
 
// both = men + women
 
// In Groovy, all objects are references, so the same problem arises.
// Typically we just return a new object. Especially for immutable objects
// this style of processing is very common.
nums = [1.4, 3.5, 6.7]
 
def toInteger(n) {
    n.collect { v -> v.toInteger() }
 
}
assert toInteger(nums) == [1, 3, 6]
 
orignums = [1.4, 3.5, 6.7]
def truncMe(n) {
 
    (0..<n.size()).each{ idx -> n[idx] = n[idx].toInteger() }
 
}
truncMe(orignums)
assert orignums == [1, 3, 6]
//----------------------------------------------------------------------------------

[править] Making Variables Private to a Function

//----------------------------------------------------------------------------------
// variable scope examples
def somefunc() {
    def variableInMethod  // private is default in a method
}
 
def name // private is default for variable in a script
 
bindingVar = 10 // this will be in the binding (sort of global)
globalArray = []
 
// In Groovy, run_check can't access a, b, or c until they are
// explicitely defined global (using leading $), even if they are
// both defined in the same scope
 
def checkAccess(x) {
    def y = 200
 
    return x + y + bindingVar // access private, param, global
}
assert checkAccess(7) == 217
 
def saveArray(ary) {
    globalArray << 'internal'
    globalArray += ary
 
}
 
saveArray(['important'])
assert globalArray == ["internal", "important"]
//----------------------------------------------------------------------------------

[править] Creating Persistent Private Variables

//----------------------------------------------------------------------------------
// you want a private persistent variable within a script method
 
 
// you could use a helper class for this
class CounterHelper {
    private static counter = 0
 
    def static next() { ++counter }
}
def greeting(s) {
 
    def n = CounterHelper.next()
    println "Hello $s  (I have been called $n times)"
}
greeting('tom')
 
greeting('dick')
greeting('harry')
// =>
// Hello tom  (I have been called 1 times)
// Hello dick  (I have been called 2 times)
// Hello harry  (I have been called 3 times)
 
// you could make it more fancy by having separate keys,
// using synchronisation, singleton pattern, ThreadLocal, ...
//----------------------------------------------------------------------------------

[править] Determining Current Function Name

//----------------------------------------------------------------------------------
// Determining Current Method Name
// Getting class, package and static info is easy. Method info is just a little work.
// From Java we can use:
//     new Exception().stackTrace[0].methodName
// or for Java 5 and above (saves relatively expensive exception creation)
//     Thread.currentThread().stackTrace[3].methodName
// But these give the Java method name. Groovy wraps its own runtime
// system over the top. It's still a Java method, just a little bit further up the
// stack from where we might expect. Getting the Groovy method name can be done in
// an implementation specific way (subject to change as the language evolves):
 
def myMethod() {
    names = new Exception().stackTrace*.methodName
    println groovyUnwrap(names)
 
}
def myMethod2() {
    names = Thread.currentThread().stackTrace*.methodName
    names = names[3..<names.size()] // skip call to dumpThread
 
    println groovyUnwrap(names)
}
def groovyUnwrap(names) { names[names.indexOf('invoke0')-1] }
 
myMethod()  // => myMethod
myMethod2() // => myMethod2
 
// Discussion: If what you really wanted was a tracing mechanism, you could overrie
// invokeMethod and print out method names before calling the original method. Or
// you could use one of the Aspect-Oriented Programming packages for Java.
//----------------------------------------------------------------------------------

[править] Passing Arrays and Hashes by Reference

//----------------------------------------------------------------------------------
// Passing Arrays and Hashes by Reference
// In Groovy, every value is a reference to an object, thus there is
// no such problem, just call: arrayDiff(array1, array2)
 
 
// pairwise add (altered so it doesn't require equal sizes)
def pairWiseAdd(a1, a2) {
    s1 = a1.size(); s2 = a2.size()
 
    (0..<[s1,s2].max()).collect{
        it > s1-1 ? a2[it] : (it > s2-1 ? a1[it] : a1[it] + a2[it])
 
    }
}
a = [1, 2]
b = [5, 8]
 
assert pairWiseAdd(a, b) == [6, 10]
 
// also works for unequal sizes
b = [5, 8, -1]
 
assert pairWiseAdd(a, b) == [6, 10, -1]
b = [5]
 
assert pairWiseAdd(a, b) == [6, 2]
 
// We could check if both arguments were of a particular type, e.g.
// (a1 instanceof List) or (a2.class.isArray()) but duck typing allows
// it to work on other things as well, so while wouldn't normally do this
// you do need to be a little careful when calling the method, e.g.
// here we call it with two maps of strings and get back strings
// the important thing here was that the arguments were indexed
// 0..size-1 and that the items supported the '+' operator (as String does)
a = [0:'Green ', 1:'Grey ']
 
b = [0:'Frog', 1:'Elephant', 2:'Dog']
assert pairWiseAdd(a, b) == ["Green Frog", "Grey Elephant", "Dog"]
 
//----------------------------------------------------------------------------------

[править] Detecting Return Context

//----------------------------------------------------------------------------------
// Detecting Return Context
// There is no exact equivalent of return context in Groovy but
// you can behave differently when called under different circumstances
def addValueOrSize(a1, a2) {
     b1 = (a1 instanceof Number) ? a1 : a1.size()
 
     b2 = (a2 instanceof Number) ? a2 : a2.size()
     b1 + b2
}
assert (addValueOrSize(10, 'abcd')) == 14
 
assert (addValueOrSize(10, [25, 50])) == 12
assert (addValueOrSize('abc', [25, 50])) == 5
 
assert (addValueOrSize(25, 50)) == 75
 
// Of course, a key feature of many OO languages including Groovy is
// method overloading so that responding to dofferent parameters has
// a formal way of being captured in code with typed methods, e.g.
class MakeBiggerHelper {
 
    def triple(List iList) { iList.collect{ it * 3 } }
 
    def triple(int i) { i * 3 }
 
}
mbh = new MakeBiggerHelper()
assert mbh.triple([4, 5]) == [12, 15]
 
assert mbh.triple(4) == 12
 
// Of course with duck typing, we can rely on dynamic typing if we want
def directTriple(arg) {
 
    (arg instanceof Number) ? arg * 3 : arg.collect{ it * 3 }
 
}
assert directTriple([4, 5]) == [12, 15]
 
assert directTriple(4) == 12
//----------------------------------------------------------------------------------

[править] Passing by Named Parameter

//----------------------------------------------------------------------------------
// Passing by Named Parameter
// Groovy supports named params or positional arguments with optional
// defaults to simplify method calling
 
// named arguments work by using a map
def thefunc(Map args) {
 
    // in this example, we just call the positional version
    thefunc(args.start, args.end, args.step)
}
 
// positional arguments with defaults
def thefunc(start=0, end=30, step=10) {
 
    ((start..end).step(step))
}
 
assert thefunc()                        == [0, 10, 20, 30]
 
assert thefunc(15)                      == [15, 25]
assert thefunc(0,40)                    == [0, 10, 20, 30, 40]
 
assert thefunc(start:5, end:20, step:5) == [5, 10, 15, 20]
 
//----------------------------------------------------------------------------------

[править] Skipping Selected Return Values

//----------------------------------------------------------------------------------
// Skipping Selected Return Values
// Groovy 1.0 doesn't support multiple return types, so you always use
// a holder class, array or collection to return multiple values.
def getSystemInfo() {
    def millis = System.currentTimeMillis()
 
    def freemem = Runtime.runtime.freeMemory()
    def version = System.getProperty('java.vm.version')
    return [millis:millis, freemem:freemem, version:version]
 
    // if you are likely to want all the information use a list
    //     return [millis, freemem, version]
    // or dedicated holder class
    //     return new SystemInfo(millis, freemem, version)
}
result = getSystemInfo()
println result.version
// => 1.5.0_08-b03
//----------------------------------------------------------------------------------

[править] Returning More Than One Array or Hash

//----------------------------------------------------------------------------------
// Returning More Than One Array or Hash
// As per 10.8, Groovy 1.0 doesn't support multiple return types but you
// just use a holder class, array or collection. There are no limitations
// on returning arbitrary nested values using this technique.
def getInfo() {
    def system = [millis:System.currentTimeMillis(),
                  version:System.getProperty('java.vm.version')]
 
    def runtime = [freemem:Runtime.runtime.freeMemory(),
                   maxmem:Runtime.runtime.maxMemory()]
    return [system:system, runtime:runtime]
 
}
println info.runtime.maxmem // => 66650112 (info automatically calls getInfo() here)
//----------------------------------------------------------------------------------

[править] Returning Failure

//----------------------------------------------------------------------------------
// Returning Failure
// This is normally done in a heavy-weight way via Java Exceptions
// (see 10.12) or in a lightweight way by returning null
def sizeMinusOne(thing) {
 
    if (thing instanceof Number) return
    thing.size() - 1
 
}
def check(thing) {
    result = sizeMinusOne(thing)
 
    println (result ? "Worked with result: $result" : 'Failed')
}
check(4)
 
check([1, 2])
check('abc')
 
// =>
// Failed
// Worked with result: 1
// Worked with result: 2
//----------------------------------------------------------------------------------

[править] Prototyping Functions

//----------------------------------------------------------------------------------
// Prototyping Functions: Not supported by Groovy but arguably
// not important given other language features.
 
 
// Omitting Parentheses Scenario: Groovy only lets you leave out
// parentheses in simple cases. If you had two methods sum(a1,a2,a3)
// and sum(a1,a2), there would be no way to indicate that whether
// 'sum sum 2, 3, 4, 5' meant sum(sum(2,3),4,5) or sum(sum(2,3,4),5).
// You would have to include the parentheses. Groovy does much less
// auto flattening than some other languages; it provides a *args
// operator, varargs style optional params and supports method
// overloading and ducktyping. Perhaps these other features mean
// that this scenario is always easy to avoid.
def sum(a,b,c){ a+b+c*2 }
def sum(a,b){ a+b }
 
// sum sum 1,2,4,5
// => compilation error
sum sum(1,2),4,5
sum sum(1,2,4),5
 
// these work but if you try to do anything fancy you will run into trouble;
// your best bet is to actually include all the parentheses:
println sum(sum(1,2),4,5) // => 17
 
println sum(sum(1,2,4),5) // => 16
 
 
// Mimicking built-ins scenario: this is a mechanism to turn-off
// auto flattening, Groovy only does flattening in restricted circumstances.
// func(array, 1, 2, 3) is never coerced into a single list but varargs
// and optional args can be used instead
def push(list, Object[] optionals) {
    optionals.each{ list.add(it) }
 
}
items = [1,2]
newItems = [7, 8, 9]
 
push items, 3, 4
push items, 6
push (items, *newItems) // brackets currently required, *=flattening
                        // without *: items = [1, 2, 3, 4, 6, [7, 8, 9]]
assert items == [1, 2, 3, 4, 6, 7, 8, 9]
 
//----------------------------------------------------------------------------------

[править] Handling Exceptions

//----------------------------------------------------------------------------------
// Handling Exceptions
// Same story as in Java but Groovy has some nice Checked -> Unchecked
// magic behind the scenes (Java folk will know what this means)
// When writing methods:
//     throw exception to raise it
// When calling methods:
//     try ... catch ... finally surrounds processing logic
def getSizeMostOfTheTime(s) {
    if (s =~ 'Full Moon') throw new RuntimeException('The world is ending')
 
    s.size()
}
try {
    println 'Size is: ' + getSizeMostOfTheTime('The quick brown fox')
 
    println 'Size is: ' + getSizeMostOfTheTime('Beware the Full Moon')
} catch (Exception ex) {
 
    println "Error was: $ex.message"
} finally {
    println 'Doing common cleanup'
}
// =>
 
// Size is: 19
// Error was: The world is ending
// Doing common cleanup
//----------------------------------------------------------------------------------

[править] Saving Global Values

//----------------------------------------------------------------------------------
// Saving Global Values
// We can just save the value and restore it later:
def printAge() { println "Age is $age" }
 
age = 18         // binding "global" variable
 
printAge()       // => 18
 
if (age > 0) {
 
    def origAge = age
    age = 23
    printAge()   // => 23
    age = origAge
 
}
printAge()       // => 18
 
// Depending on the circmstances we could enhance this in various ways
// such as synchronizing, surrounding with try ... finally, using a
// memento pattern, saving the whole binding, using a ThreadLocal ...
 
// There is no need to use local() for filehandles or directory
// handles in Groovy because filehandles are normal objects.
//----------------------------------------------------------------------------------

[править] Redefining a Function

//----------------------------------------------------------------------------------
// Redefining a Function
// This can be done via a number of ways:
 
// OO approach:
// The standard trick using OO is to override methods in subclasses
class Parent { def foo(){ println 'foo' } }
 
class Child extends Parent { def foo(){ println 'bar' } }
 
new Parent().foo()   // => foo
new Child().foo()    // => bar
 
 
// Category approach:
// If you want to redefine a method from an existing library
// you can use categories. This can be done to avoid name conflicts
// or to patch functionality with local mods without changing
// original code
println new Date().toString()
// => Sat Jan 06 16:44:55 EST 2007
class DateCategory {
 
    static toString(Date self) { 'not telling' }
}
 
use (DateCategory) {
    println new Date().toString()
}
 
// => not telling
 
// Closure approach:
// Groovy's closures let you have "anonymous methods" as objects.
// This allows you to be very flexible with "method" redefinition, e.g.:
colors = 'red yellow blue green'.split(' ').toList()
color2html = new Expando()
 
colors.each { c ->
    color2html[c] = { args -> "<FONT COLOR='$c'>$args</FONT>" }
 
}
println color2html.yellow('error')
// => <FONT COLOR='yellow'>error</FONT>
color2html.yellow = { args -> "<b>$args</b>" } // too hard to see yellow
 
println color2html.yellow('error')
// => <b>error</b>
 
// Other approaches:
// you could use invokeMethod to intercept the original method and call
// your modified method on just particular input data
//----------------------------------------------------------------------------------

[править] Trapping Undefined Function Calls with AUTOLOAD

//----------------------------------------------------------------------------------
// Trapping Undefined Function Calls
 
class FontHelper {
    // we could define all the important colors explicitly like this
    def pink(info) {
 
        buildFont('hot pink', info)
    }
    // but this method will catch any undefined ones
    def invokeMethod(String name, Object args) {
 
        buildFont(name, args.join(' and '))
    }
    def buildFont(name, info) {
 
        "<FONT COLOR='$name'>" + info + "</FONT>"
    }
}
fh = new FontHelper()
 
println fh.pink("panther")
println fh.chartreuse("stuff", "more stuff")
// =>
// <FONT COLOR='hot pink'>panther</FONT>
 
// <FONT COLOR='chartreuse'>stuff and more stuff</FONT>
//----------------------------------------------------------------------------------

[править] Nesting Subroutines

//----------------------------------------------------------------------------------
// Simulating Nested Subroutimes: Using Closures within Methods
def outer(arg) {
 
    def x = arg + 35
    inner = { x * 19 }
 
    x + inner()
}
assert outer(10) == 900
//----------------------------------------------------------------------------------

[править] Program: Sorting Your Mail

//----------------------------------------------------------------------------------
// Program: Sorting Your Mail
#!/usr/bin/groovy
import javax.mail.*
 
// solution using mstor package (mstor.sf.net)
session = Session.getDefaultInstance(new Properties())
 
store = session.getStore(new URLName('mstor:/path_to_your_mbox_directory'))
store.connect()
 
// read messages from Inbox
inbox = store.defaultFolder.getFolder('Inbox')
 
inbox.open(Folder.READ_ONLY)
messages = inbox.messages.toList()
 
// extractor closures
subject = { m -> m.subject }
 
subjectExcludingReplyPrefix = { m -> subject(m).replaceAll(/(?i)Re:\\s*/,'') } // double slash to single outside triple quotes
 
date = { m -> d = m.sentDate; new Date(d.year, d.month, d.date) } // ignore time fields
 
 
// sort by subject excluding 'Re:' prefixs then print subject for first 6
println messages.sort{subjectExcludingReplyPrefix(it)}[0..5]*.subject.join('\n')
 
// =>
// Additional Resources for JDeveloper 10g (10.1.3)
// Amazon Web Services Developer Connection Newsletter #18
// Re: Ant 1.7.0?
// ARN Daily | 2007: IT predictions for the year ahead
// Big Changes at Gentleware
// BigPond Account Notification
 
// sort by date then subject (print first 6 entries)
sorted = messages.sort{ a,b ->
    date(a) == date(b) ?
        subjectExcludingReplyPrefix(a) <=> subjectExcludingReplyPrefix(b) :
        date(a) <=> date(b)
 
}
sorted[0..5].each{ m -> println "$m.sentDate: $m.subject" }
 
// =>
// Wed Jan 03 08:54:15 EST 2007: ARN Daily | 2007: IT predictions for the year ahead
// Wed Jan 03 15:33:31 EST 2007: EclipseSource: RCP Adoption, Where Art Thou?
// Wed Jan 03 00:10:11 EST 2007: What's New at Sams Publishing?
// Fri Jan 05 08:31:11 EST 2007: Building a Sustainable Open Source Business
// Fri Jan 05 09:53:45 EST 2007: Call for Participation: Agile 2007
// Fri Jan 05 05:51:36 EST 2007: IBM developerWorks Weekly Edition, 4 January 2007
 
// group by date then print first 2 entries of first 2 dates
groups = messages.groupBy{ date(it) }
groups.keySet().toList()[0..1].each{
 
    println it
    println groups[it][0..1].collect{ '    ' + it.subject }.join('\n')
 
}
// =>
// Wed Jan 03 00:00:00 EST 2007
//     ARN Daily | 2007: IT predictions for the year ahead
//     EclipseSource: RCP Adoption, Where Art Thou?
// Fri Jan 05 00:00:00 EST 2007
//     Building a Sustainable Open Source Business
//     Call for Participation: Agile 2007