awesome groovy
TRANSCRIPT
Dr Paul KingGroovy Lead for Object Computing Inc.@paulk_asert
http:/slideshare.net/paulk_asert/awesome-groovy
https://github.com/paulk-asert/awesome-groovy
Apache Groovy:
The Awesome Parts
FRIEND OF GROOVY
Part 1: Introducing Groovy?
What is Groovy?
4
Groovy = Java – boiler plate code+ closures (1st class FP)+ extensible type system+ runtime & compile-time metaprogramming+ flexible language grammar (DSLs)+ scripting+ GDK library
“Groovy is like a super version of Java.
It leverages Java features but adds productivity features
and provides great flexibility and extensibility.”
Why Groovy?
Java
Why Groovy?
Too verboseSupport
functional
programming
Better
concurrency
Evolves
too slowly
I need
feature
XXX
Too complex
for some
situations
More
flexibility
Java
Why Groovy?
Groovy
Make it
simplerMake it
dynamic
Support
simple
scripting
Why Groovy?
Groovy
Make it
simplerMake it
dynamic
Support
simple
scripting
Java
integration
Why Groovy?
Groovy
Make it
simplerMake it
dynamic
Support
simple
scripting
Good IDE
support
Java
integration
Why Groovy?
Groovy
Make it
simplerMake it
dynamic
Support
simple
scripting
Good IDE
support
Custom
features
Java
integration
Why Groovy?
Groovy
Make it
simplerMake it
dynamic
Support
simple
scripting
Support
concurrency
Good IDE
support
Custom
features
Java
integration
Why Groovy?
Groovy
Make it
simplerMake it
dynamic
Support
simple
scripting
Support
functional
style
Support
concurrency
Good IDE
support
Custom
features
Java
integration
Java code for list manipulationimport java.util.List;import java.util.ArrayList;
class Main {private List keepShorterThan(List strings, int length) {
List result = new ArrayList();for (int i = 0; i < strings.size(); i++) {
String s = (String) strings.get(i);if (s.length() < length) {
result.add(s);}
}return result;
}public static void main(String[] args) {
List names = new ArrayList();names.add("Ted"); names.add("Fred");names.add("Jed"); names.add("Ned");System.out.println(names);Main m = new Main();List shortNames = m.keepShorterThan(names, 4);System.out.println(shortNames.size());for (int i = 0; i < shortNames.size(); i++) {
String s = (String) shortNames.get(i);System.out.println(s);
}}
}
Groovy code for list manipulationimport java.util.List;import java.util.ArrayList;
class Main {private List keepShorterThan(List strings, int length) {
List result = new ArrayList();for (int i = 0; i < strings.size(); i++) {
String s = (String) strings.get(i);if (s.length() < length) {
result.add(s);}
}return result;
}public static void main(String[] args) {
List names = new ArrayList();names.add("Ted"); names.add("Fred");names.add("Jed"); names.add("Ned");System.out.println(names);Main m = new Main();List shortNames = m.keepShorterThan(names, 4);System.out.println(shortNames.size());for (int i = 0; i < shortNames.size(); i++) {
String s = (String) shortNames.get(i);System.out.println(s);
}}
}
Rename
Main.javatoMain.groovy
Some Java Boilerplate identifiedimport java.util.List;import java.util.ArrayList;
class Main {private List keepShorterThan(List strings, int length) {
List result = new ArrayList();for (int i = 0; i < strings.size(); i++) {
String s = (String) strings.get(i);if (s.length() < length) {
result.add(s);}
}return result;
}public static void main(String[] args) {
List names = new ArrayList();names.add("Ted"); names.add("Fred");names.add("Jed"); names.add("Ned");System.out.println(names);Main m = new Main();List shortNames = m.keepShorterThan(names, 4);System.out.println(shortNames.size());for (int i = 0; i < shortNames.size(); i++) {
String s = (String) shortNames.get(i);System.out.println(s);
}}
}
Are the semicolons
needed?
And shouldn’t
we us more
modern list
notation?
Why not
import common
libraries?
Do we need
the static types?
Must we always
have a main
method and
class definition?
How about
improved
consistency?
Java Boilerplate removeddef keepShorterThan(strings, length) {
def result = new ArrayList()for (s in strings) {
if (s.size() < length) {result.add(s)
}}return result
}
names = new ArrayList()names.add("Ted"); names.add("Fred")names.add("Jed"); names.add("Ned")System.out.println(names)shortNames = keepShorterThan(names, 4)System.out.println(shortNames.size())for (s in shortNames) {
System.out.println(s)}
More Java Boilerplate identifieddef keepShorterThan(strings, length) {
def result = new ArrayList()for (s in strings) {
if (s.size() < length) {result.add(s)
}}return result
}
names = new ArrayList()names.add("Ted"); names.add("Fred")names.add("Jed"); names.add("Ned")System.out.println(names)shortNames = keepShorterThan(names, 4)System.out.println(shortNames.size())for (s in shortNames) {
System.out.println(s)}
Shouldn’t we
have special
notation for lists?
And special
facilities for
list processing?
Is ‘return’
needed at end?
Is the method
now needed?
Simplify common
methods?
Remove unambiguous
brackets?
Boilerplate removed = nicer Groovy version
names = ["Ted", "Fred", "Jed", "Ned"]println namesshortNames = names.findAll{ it.size() < 4 }println shortNames.size()shortNames.each{ println it }
["Ted", "Fred", "Jed", "Ned"]3TedJedNed
Output:
Or Groovy DSL version if required
given the names "Ted", "Fred", "Jed" and "Ned"display all the namesdisplay the number of names having size less than 4display the names having size less than 4
// plus a DSL implementation
Or Groovy DSL version if required
given the names "Ted", "Fred", "Jed" and "Ned"display all the namesdisplay the number of names having size less than 4display the names having size less than 4
names = []def of, having, lessdef given(_the) { [names:{ Object[] ns -> names.addAll(ns)[and: { n -> names += n }] }] }
def the = [number: { _of -> [names: { _having -> [size: { _less -> [than: { size ->println names.findAll{ it.size() < size }.size() }]}] }] },
names: { _having -> [size: { _less -> [than: { size ->names.findAll{ it.size() < size }.each{ println it } }]}] }
]def all = [the: { println it }]def display(arg) { arg }
Or Groovy DSL version if required
• Or use GDSL (IntelliJ IDEA) or DSLD (Eclipse)
given the names "Ted", "Fred", "Jed" and "Ned"display all the namesdisplay the number of names having size less than 4display the names having size less than 4
Or typed Groovy DSL version if required
given the names "Ted", "Fred", "Jed" and "Ned"display all the namesdisplay the number of names having size less than 4display the names having size less than 4
…enum The { the }enum Having { having }enum Of { of }…class DisplayThe {
DisplayTheNamesHaving names(Having having) {new DisplayTheNamesHaving()
}DisplayTheNumberOf number(Of of) {
new DisplayTheNumberOf()}
}…// plus 50 lines
Or typed Groovy DSL version if required
Groovy DSL being debugged
Or typed Groovy DSL version if required@TypeChecked(extensions='EdChecker.groovy')def method() {
given the names "Ted", "Fred", "Jed" and "Ned"display all the namesdisplay the number of names having size less than 4display the names having size less than 4
}
Or typed Groovy DSL version if required@TypeChecked(extensions='EdChecker.groovy')def method() {
given the names "Ted", "Fred", "Jed" and "Ned"display all the namesdisplay the number of names having size less than 4display the names having size less than 4
}
afterMethodCall { mc ->mc.arguments.each {
if (isConstantExpression(it)) {if (it.value instanceof String && !it.value.endsWith('ed')) {
addStaticTypeError("I don't like the name '${it.value}'", mc)}
}}
}
@TypeChecked(extensions='EdChecker.groovy')def method() {
given the names "Ted", "Fred", "Jed" and "Ned"display all the namesdisplay the number of names having size less than 4display the names having size less than 4
}
Or typed Groovy DSL version if required
afterMethodCall { mc ->mc.arguments.each {
if (isConstantExpression(it)) {if (it.value instanceof String && !it.value.endsWith('ed')) {
addStaticTypeError("I don't like the name '${it.value}'", mc)}
}}
}
@TypeChecked(extensions='EdChecker.groovy')def method() {
given the names "Ted", “Mary", "Jed" and “Pete"display all the namesdisplay the number of names having size less than 4display the names having size less than 4
}
afterMethodCall { mc ->mc.arguments.each {
if (isConstantExpression(it)) {if (it.value instanceof String && !it.value.endsWith('ed')) {
addStaticTypeError("I don't like the name '${it.value}'", mc)}
}}
}
Or typed Groovy DSL version if required
@TypeChecked(extensions='EdChecker.groovy')def method() {
given the names "Ted", “Mary", "Jed" and “Pete"display all the namesdisplay the number of names having size less than 4display the names having size less than 4
}
afterMethodCall { mc ->mc.arguments.each {
if (isConstantExpression(it)) {if (it.value instanceof String && !it.value.endsWith('ed')) {
addStaticTypeError("I don't like the name '${it.value}'", mc)}
}}
}
Or typed Groovy DSL version if required
What style of language is Groovy?
Groovy Style
• Imperative/OO but somewhat
paradigm agnostic
• Dynamic, optionally static (gradual
extensible typing)
• Extensible language features
through metaprogramming
Part 2: What makes Groovy Awesome?
What makes Groovy Awesome?
• Java integration
• Scripting support
• Multiparadigm
• Gradual typing
• Metaprogramming
• Domain specific
language (DSL) support
• Ecosystem
• Community/Team
Awesome
What makes Groovy Awesome?
• Java integration
• Builds up on Java
• Tight integration
• Polyglot friendly
• Scripting support
• Multiparadigm
• Gradual typing
• Metaprogramming
• Domain Specific Language support
• Ecosystem
• Community/Team
Java Integration
• Groovy is Java’s friend
Java
Groovy
Java Integration
• Standing on the shoulders of Giants
• Some limitations inherited but much
gained through new releases of Java
• Rock solid foundation
• Can ease migration to new versions
of Java
Java
Groovy
Java Integration
• Seamless integration
• IDEs provide cross-language compile,
navigation, and refactoring
• Arbitrarily mix source language
• Drop-in replace
any class
• Overloaded methods
• Syntax alignment
• Shared data types
Java
Groovy
Java Integration
• Seamless integration
• IDEs provide cross-language compile,
navigation, and refactoring
• Arbitrarily mix source language
• Drop-in replace
any class
• Overloaded methods
• Syntax alignment
• Shared data types
Java
Groovy
Java Integration
• Polyglot friendly• Typically integrates well with other
languages which integrate with Java
• JRuby
• Jython
• Scala
• Frege
• Clojure
• R through Renjin
• JavaScript(Rhino/Nashorn)
Java Integration
• Polyglot friendly
• JSR-223 scripting
to talk to JavaScript
import javax.script.ScriptEngineManager
def mgr = new ScriptEngineManager()def engine = mgr.getEngineByName('nashorn')assert engine.eval('''function factorial(n) {if (n == 0) { return 1; }return n * factorial(n - 1);
}factorial(4)
''') == 24.0
Java
GroovyJavaScript
Java Integration• Polyglot friendly: R integration
@GrabResolver('https://nexus.bedatadriven.com/content/groups/public')@Grab('org.renjin:renjin-script-engine:0.7.0-RC7')import javax.script.ScriptEngineManager
def mgr = new ScriptEngineManager()def engine = mgr.getEngineByName('Renjin')
engine.with {eval '''
factorial <- function(x) {y <- 1for (i in 1:x) { y <- y * i }return(y)
}'''assert eval('factorial(4)')[0] == 24
}
Java
GroovyR
Grooscript
@Grab('org.grooscript:grooscript:1.2.0')import org.grooscript.GrooScriptimport javax.script.ScriptEngineManager
def groojs = GrooScript.convert '''def sayHello = { println "Hello ${it}!" }['Groovy','JavaScript','GrooScript'].each sayHello''', [initialText: 'var console = { log: print }',addGsLib: 'grooscript.min']
new ScriptEngineManager().getEngineByName("nashorn").eval(groojs)
Hello Groovy!
Hello JavaScript!
Hello GrooScript!
Jorge Franco, grooscript.org
Convert Groovy subset to JavaScript
Support: Gradle, Grails, NPM
Demos: react.js, node.js, Pollock
Converts: Classes, closures, Date
primitives, ranges, String/GString
Special: builder, templates, gQuery
What makes Groovy Awesome?
• Java integration
• Builds up on Java
• Tight integration
• Polyglot friendly
• Scripting support
• Multiparadigm
• Gradual typing
• Metaprogramming
• Domain Specific Language support
• Ecosystem
• Community/Team
Awesome
• Friend of Java
• Integration
• Good JVM Citizen
What makes Groovy Awesome?
• Java integration
• Scripting support
• Simplified scripting
• Separation of concerns
• Security sandbox
• Multiparadigm
• Gradual typing
• Metaprogramming
• Domain Specific Language support
• Ecosystem
• Community/Team
Awesome
Scripting
• Terse format
• Isolation
• Domain-Specific
Languages (DSLs)
• Security
• Flexibility
Scripting
• Leave out surrounding class
• Entire source file:
public class Script {public static void main(String[] args) {System.out.println("Hello world");
}}
println "Hello world"
Scripting
• Various approaches supported
• GroovyShell
• GroovyScriptEngine
• GroovyClassLoader
• Spring
• JSR-223
• Base scripts
• allows predefined methods
• Binding
• allows predefined variables
• Customizers
• add imports, static imports
• lock down AST to a subset
• apply any AST transform transparently
Scripting
Scripting
Scripting
Scripting
class Robot {void move(dir) {println "robot moved $dir"
}}
def robot = new Robot()
Scripting
import static Direction.*
enum Direction {left, right, forward, backward
}
class Robot {void move(Direction dir) {println "robot moved $dir"
}}
def robot = new Robot()
robot.move left
Scripting
move left
def robot = new Robot()def binding = new Binding(robot: robot,move: robot.&move,*: Direction.values().collectEntries {
[(it.name()): it]}
)
def shell = new GroovyShell(binding)shell.evaluate new File(args[0]).text
Scripting
move left
def robot = new Robot()def binding = new Binding(/* ... */)def imports = new ImportCustomizer().addStaticStars('java.lang.Math')def timeout = new ASTTransformationCustomizer(value: 2, TimedInterrupt)def secure = new SecureASTCustomizer().with {
closuresAllowed = falseimportsWhitelist = []tokensWhitelist = [ PLUS, MINUS, MULTIPLY, DIVIDE, /*...*/ ]//...
}def sysExit = new CompilationCustomizer(CANONICALIZATION) {
void call(SourceUnit src, GeneratorContext ctxt, ClassNode cn) {new ClassCodeVisitorSupport() {
void visitMethodCallExpression(MethodCallExpression call) {// check for System.exit()
}// ...
}.visitClass(cn)}
}def config = new CompilerConfiguration()config.addCompilationCustomizers(imports, timeout, secure, sysExit)def shell = new GroovyShell(binding, config)
Scripting
move left
move forward at 3.km/h
More
examples
later
What makes Groovy Awesome?
• Java integration
• Scripting support
• Simplified scripting
• Separation of concerns
• Security sandbox
• Multiparadigm
• Gradual typing
• Metaprogramming
• Domain Specific Language support
• Ecosystem
• Community/Team
Awesome
• Simpler scripting
• Separation of concerns
What makes Groovy Awesome?
• Java integration
• Scripting support
• Multiparadigm
• Object-oriented
• Functional, Closures
• Logic/Dataflow programming• Gradual typing
• Metaprogramming
• Domain Specific Language support
• Ecosystem
• Community/Team
Multiparadigm
• Imperative roots from Java
• OO abstractions: classes,
interfaces, inheritance
• Properties
• Traits
• First class functional support via closures
• External libraries for advanced features
• Other paradigms via libraries
• Logic, dataflow, reactive
Awesome
Multiparadigm
Imperative/OO
Logic programming
Functional
Rich OO features
• Classes (and scripts, GroovyBeans)
• Fields (and properties)
• Methods, constructors, multi-methods, named and default parameters
• Inheritance (behavior and implementation), interfaces, traits
• Type aliases
• Packages
• Special accessor notation, spread operators, GPath, safe navigation
Traits
/*** Things that hop* @author Grace*/trait Hopper {
String jump() { "I'm jumping!" }}
class Kangaroo implements Hopper {}
def skip = new Kangaroo()
assert skip.jump() == "I'm jumping!"
Rich functional features
• Closures
• First-class functions
• Higher-order functions
• Map, reduce, filter
• Mutable & immutable data
• Recursion
• Lazy & eager evaluation
• Advanced FP techniques
• Memoization, Trampolines, Composition, Curry
• Concurrency
map/filter/reduce
@Canonicalclass Person {String nameint age
}
def people = [new Person('Peter', 45),new Person('Paul', 35),new Person('Mary', 25)
]
assert people.findAll{ it.age < 40 }.collect{ it.name.toUpperCase() }.sort().join(', ') == 'MARY, PAUL'
map/filter/reduce (+ functional style)
@Canonicalclass Person {String nameint age
}
def people = [new Person('Peter', 45),new Person('Paul', 35),new Person('Mary', 25)
]
assert people.findAll{ it.age < 40 }.collect{ it.name.toUpperCase() }.sort().join(', ') == 'MARY, PAUL'
def young = { person, threshold ->person.age < threshold
}.rcurry(40).memoize()
assert people.findAll(young).collect{ it.name.toUpperCase() }.sort().join(', ') == 'MARY, PAUL'
map/filter/reduce (+ with streams)
@Canonicalclass Person {String nameint age
}
def people = [new Person('Peter', 45),new Person('Paul', 35),new Person('Mary', 25)
]
assert people.findAll{ it.age < 40 }.collect{ it.name.toUpperCase() }.sort().join(', ') == 'MARY, PAUL'
// requires JRE 8def commaSep = Collectors.joining(", ")assert people.stream()
.filter{ it.age < 40 }
.map{ it.name.toUpperCase() }
.sorted()
.collect(commaSep) == 'MARY, PAUL'
Logic/Constraint programming
• Logic programming
• Declarative style
• Logic clauses for example Prolog
• Constraint programming
• Declarative style similar to logic programming but contain constraints which
must be satisfied
• Relations between variables are stated as constraints
• Not a step or sequence of steps to execute, but rather the properties of a
solution to be found
Logic programming example
cranes have 2 legstortoises have 4 legs
there are 7 animalsthere are 20 legs
How many of each animal?
Constraint programming example
@Grab('org.choco-solver:choco-solver:4.0.4')import org.chocosolver.solver.Model
def m = new Model()
def totalAnimals = 7def totalLegs = 20def numCranes = m.intVar('Cranes', 0, totalAnimals, true)def numTortoises = m.intVar('Tortoises', 0, totalAnimals, true)def numCraneLegs = m.intScaleView(numCranes, 2)def numTortoiseLegs = m.intScaleView(numTortoises, 4)m.arithm(numCranes, '+', numTortoises, '=', totalAnimals).post()m.arithm(numCraneLegs, '+', numTortoiseLegs, '=', totalLegs).post()if (m.solver.solve())println "$numCranes\n$numTortoises"
elseprintln "No Solutions" Cranes = 4
Tortoises = 3
ChocoCraneTortoise.groovy
See also: JSR-331 Constraint Programming API
Dataflow programming
• Declarative style
• Emphasizes the movement of data
• models programs as a series of connected tasks
• A task has explicitly defined inputs and outputs
• runs as soon as all of its inputs become available
• Inherently parallel
GPars
• Library classes and DSL allowing
you to handle tasks concurrently:
• Data Parallelism map, filter, reduce functionality
in parallel with parallel array support
• Asynchronous functions extend the Java executor
services to enable multi-threaded closure processing
• Dataflow Concurrency supports natural
shared-memory concurrency model, using
single-assignment variables
• Actors provide Erlang/Scala-like actors
including "remote" actors on other machines
• Safe Agents provide a non-blocking mt-safe
reference to mutable state; like "agents" in Clojure
72
Awesome
Concurrency challenge
• We can analyse the example’s task graph:
def (f1, f2, f3, f4) = [{ sleep 1000; it }] * 3 +[{ x, y -> x + y }]
def a = 5
def b = f1(a)def c = f2(a)def d = f3(c)def f = f4(b, d)
assert f == 10
Concurrency challenge
• We can analyse the example’s task graph:
def (f1, f2, f3, f4) = [{ sleep 1000; it }] * 3 +[{ x, y -> x + y }]
def a = 5
def b = f1(a)def c = f2(a)def d = f3(c)def f = f4(b, d)
assert f == 10
f2
f3
f1
f4
aa
bc
d
f
Concurrency challenge
• Manually using asynchronous functions:
def (f1, f2, f3, f4) = [{ sleep 1000; it }] * 3 +[{ x, y -> x + y }]
import static groovyx.gpars.GParsPool.withPool
withPool(2) {def a = 5
def futureB = f1.callAsync(a)def c = f2(a)def d = f3(c)def f = f4(futureB.get(), d)
assert f == 10}
f2
f3
f1
f4
aa
futureB
c
d
f
Concurrency challenge
• And with GPars Dataflows:
def (f1, f2, f3, f4) = [{ sleep 1000; it }] * 3 +[{ x, y -> x + y }]
import groovyx.gpars.dataflow.Dataflowsimport static groovyx.gpars.dataflow.Dataflow.task
new Dataflows().with {task { a = 5 }task { b = f1(a) }task { c = f2(a) }task { d = f3(c) }task { f = f4(b, d) }assert f == 10
}
f2
f3
f1
f4
aa
bc
d
f
Concurrency challenge
• And with GPars Dataflows:
def (f1, f2, f3, f4) = [{ sleep 1000; it }] * 3 +[{ x, y -> x + y }]
import groovyx.gpars.dataflow.Dataflowsimport static groovyx.gpars.dataflow.Dataflow.task
new Dataflows().with {task { f = f4(b, d) }task { d = f3(c) }task { c = f2(a) }task { b = f1(a) }task { a = 5 }assert f == 10
}
f2
f3
f1
f4
aa
bc
d
f
What makes Groovy Awesome?
• Java integration
• Scripting support
• Multiparadigm
• Object-oriented
• Functional, Closures
• Logic/dataflow programming• Gradual typing
• Metaprogramming
• Domain Specific Language support
• Ecosystem
• Community/Team
• Ability to use imperative
when needed for speed
• Numerous declarative
approaches available
when it makes senseAwesome
What makes Groovy Awesome?
• Java integration
• Scripting support
• Multiparadigm
• Gradual typing
• Dynamic strong typing
• Static type checking
• Extensible
• Type inference
• Metaprogramming
• Domain Specific Language support
• Ecosystem
• Community/Team
Gradual typing
• Dynamic by default
• Gradual typing
• Static type checking
• Extensible type system
Awesome
Dynamic vs static
82
def myPets = ['Lassie', 'Skip']
List<String> yourPets = ['Lassie', 'Skip']
shouldFail(ClassCastException) {
List ourPets = new Date()
}
Dynamic vs static
83
Gradual
Type-time?
def myPets = ['Lassie', 'Skip']
List<String> yourPets = ['Lassie', 'Skip']
shouldFail(ClassCastException) {
List ourPets = new Date()
}
Dynamic vs static
84
def myPets = ['Lassie', 'Skip']
List<String> yourPets = ['Lassie', 'Skip']
shouldFail(ClassCastException) {
List ourPets = new Date()
}
def adder = { a, b -> a + b }
assert adder(100, 200) == 300
assert adder('X', 'Y') == 'XY'
Dynamic vs static
85
def adder = { a, b -> a + b }
assert adder(100, 200) == 300
assert adder('X', 'Y') == 'XY'
def myPets = ['Lassie', 'Skip']
List<String> yourPets = ['Lassie', 'Skip']
shouldFail(ClassCastException) {
List ourPets = new Date()
}Duck-typing
Dynamic vs static
86
def adder = { a, b -> a + b }
assert adder(100, 200) == 300
assert adder('X', 'Y') == 'XY'
def myPets = ['Lassie', 'Skip']
List<String> yourPets = ['Lassie', 'Skip']
shouldFail(ClassCastException) {
List ourPets = new Date()
}
@TypeChecked
def myMethod() {
def myPets = ['Lassie', 'Skip']
List yourPets = ['Lassie', 'Skip']
}
shouldFail(CompilationFailedException) {
assertScript '''
@groovy.transform.TypeChecked
def yourMethod() {
List ourPets = new Date()
}
'''
}
Dynamic vs static
87
def adder = { a, b -> a + b }
assert adder(100, 200) == 300
assert adder('X', 'Y') == 'XY'
def myPets = ['Lassie', 'Skip']
List<String> yourPets = ['Lassie', 'Skip']
shouldFail(ClassCastException) {
List ourPets = new Date()
}
@TypeChecked
def myMethod() {
def myPets = ['Lassie', 'Skip']
List yourPets = ['Lassie', 'Skip']
}
shouldFail(CompilationFailedException) {
assertScript '''
@groovy.transform.TypeChecked
def yourMethod() {
List ourPets = new Date()
}
'''
}
Extensible
Inference
Extensible type checking
import groovy.transform.TypeCheckedimport experimental.SprintfTypeChecker
@TypeChecked(extensions=SprintfTypeChecker)void main() {
sprintf('%s will turn %d on %tF', 'John', new Date(), 21)}
[Static type checking] - Parameter types didn't match types expected from the format String:For placeholder 2 [%d] expected 'int' but was 'java.util.Date'For placeholder 3 [%tF] expected 'java.util.Date' but was 'int'
sprintf has an Object varargs parameter, hence not normally
amenable to further static checking but for constant Strings
we can do better using a custom type checking plugin.
Extensible type checking
import groovy.sql.Sqlimport groovy.transform.TypeChecked
@TypeChecked(extensions='SQLExtension.groovy')findAthletes(Sql sql) {
sql.eachRow('select * frm Athlete') { row -> println row }}
SqlTC.groovy: 7: [Static type checking] - SQL query is not valid: net.sf.jsqlparser.JSQLParserException@ line 6, column 15.
sql.eachRow('select * frm Athlete') { row -> println row }^
1 error
Static compilation
import groovy.transform.*
class Actor {String firstName, lastName
@CompileStaticString getFullName() { "$firstName $lastName" }
void makePeace() {new AntBuilder().echo('Peace was never an option')
}}
def magneto = new Actor(firstName: 'Ian', lastName: 'McKellen')assert magneto.fullName == 'Ian McKellen'magneto.makePeace()
Static compilation speed
0
5
10
15
20
25
30
Fibonacci micro-benchmark
What makes Groovy Awesome?
• Java integration
• Scripting support
• Multiparadigm
• Gradual typing
• Dynamic strong typing
• Static type checking
• Extensible
• Type inference
• Metaprogramming
• Domain Specific Language support
• Ecosystem
• Community/Team
• Maximise duck-typing
• Minimise noise
• yet type inference
• Flexibility at runtime
• As strict as you want
when needed
• Fast when needed
Awesome
What makes Groovy Awesome?
• Java integration
• Scripting support
• Multiparadigm
• Gradual typing
• Metaprogramming
• Runtime
• Compile-time
• Macros
• Domain Specific Language support
• Ecosystem
• Community/Team
Runtime metaprogramming
• Add instance & static methods, constructors,
properties at runtime
• Intercept method/property access
• Catch missing methods, properties
• Used for dynamic builders, aspect-oriented
programming, test stubs, mocks & dummies
Awesome*
* Individual levels of awesomeness may vary
Runtime metaprogramming
• Adding methods at runtime
assert 'Hello'.reverse() == 'olleH'
String.metaClass.swapCase = { delegate.collect{
it in 'A'..'Z' ? it.toLowerCase() : it.toUpperCase()
}.join() }
assert 'Hello'.swapCase() == 'hELLO'
Runtime metaprogramming
• Intercepting methods
class Foo {
def one() { println "Called one()" }
def methodMissing(String name, params) {
println "Attempted $name($params)"
}
}
def f = new Foo()
f.one()
f.two()
f.three('Some Arg')
Called one()
Attempted two([])
Attempted three([Some Arg])
XML Parsing/GPath expressions
def xml = '''
<hosts>
<host name='MyHost'>
<service name='MyMicroService'/>
<service name='MyNanoService'/>
</host>
</hosts>
'''
def hosts = new XmlParser().parseText(xml)
assert hosts.host.service[0].@name=='MyMicroService'
def xml = '''
<hosts>
<host name='MyHost'>
<service name='MyMicroService'/>
<service name='MyNanoService'/>
</host>
</hosts>
'''
def hosts = new XmlParser().parseText(xml)
assert hosts.host.service[0].@name=='MyMicroService'
XML Parsing/GPath expressions
Compile-time metaprogramming
• Gives you the ability to change the language by augmenting the
compilation processAwesome
Compile-time metaprogramming
• Modify the program
at compile-time@ToString
class Person {
String first, last
}
println new Person(first: 'John',
last: 'Smith')
// => Person(John, Smith)
Compile-time metaprogramming
• Modify the program
at compile-time
class Person {
String first, last
String toString() {
"Person($first, $last)"
}
}
println new Person(first: 'John',
last: 'Smith')
// => Person(John, Smith)
Parsing Summary
public run()
...
L1
ALOAD 1
LDC 1
AALOAD
ALOAD 0
LDC "Howdy Y'all"
INVOKEINTERFACE callCurrent()
ARETURN
...
println "Howdy Y'all"
BlockStatement
-> ReturnStatement
-> MethodCallExpression
-> VariableExpression("this")
-> ConstantExpression("println")
-> ArgumentListExpression
-> ConstantExpression("Howdy Y'all")
MyScript.groovy
> groovy MyScript.groovy
> groovyc MyScript.groovy
> groovysh
> groovyConsole
• 9 phase compiler
– Early stages: read source code
and convert into a sparse syntax
tree
– Middle stages: iteratively build up
a more dense and information rich
version of the syntax tree
– Later stages: check the tree and
convert it into byte code/class files
Initialization
Semantic Analysis
Instruction Selection
Parsing
Conversion
Canonicalization
Class Generation
Output
Finalization
Parsing Summary
Parsing - Early Stages
Initialization
Semantic Analysis
Instruction Selection
Parsing
Conversion
Canonicalization
Class Generation
Output
Finalization
@ToString
class Greeter {
String message = "Howdy Y'all"
void greet() {
println message
}
}
ClassNode: Greeter
MethodNode: greet
Property: message
type: unresolved(String) AnnotationNode:
ToString
type:
unresolved(ToString)
methods:
properties:
annotations:
BlockStatement
MethodCall:
this.println(message)
Parsing - Middle Stages
Initialization
Semantic Analysis
Instruction Selection
Parsing
Conversion
Canonicalization
Class Generation
Output
Finalization
ClassNode: Greeter
MethodNode: greet
FieldNode: message
type: resolved(String)
methods:
fields:
constructors:ConstructorNode
MethodNode:
getMessageMethodNode:
setMessageMethodNode: toString
MethodNode:
getMetaClass…
Parsing - Final Stages
106
Initialization
Semantic Analysis
Instruction Selection
Parsing
Conversion
Canonicalization
Class Generation
Output
Finalization
public greet()V
...
L1
...
ALOAD 0
GETFIELD Greeter.message
INVOKEINTERFACE callCurrent()
POP
...
Immutable Classes• Some Rules
• Don’t provide mutators
• Ensure that no methods canbe overridden
o Easiest to make the class final
o Or use static factories & non-publicconstructors
• Make all fields final
• Make all fields private
o Avoid even public immutable constants
• Ensure exclusive access to any mutable components
o Don’t leak internal references
o Defensive copying in and out
• Optionally provide equals and hashCode methods
• Optionally provide toString method
@Immutable...• Java Immutable Class
• As per Joshua Bloch Effective Java
public final class Person {private final String first;private final String last;
public String getFirst() {return first;
}
public String getLast() {return last;
}
@Overridepublic int hashCode() {
final int prime = 31;int result = 1;result = prime * result + ((first == null)
? 0 : first.hashCode());result = prime * result + ((last == null)
? 0 : last.hashCode());return result;
}
public Person(String first, String last) {this.first = first;this.last = last;
}// ...
// ...@Overridepublic boolean equals(Object obj) {
if (this == obj)return true;
if (obj == null)return false;
if (getClass() != obj.getClass())return false;
Person other = (Person) obj;if (first == null) {
if (other.first != null)return false;
} else if (!first.equals(other.first))return false;
if (last == null) {if (other.last != null)
return false;} else if (!last.equals(other.last))
return false;return true;
}
@Overridepublic String toString() {
return "Person(first:" + first+ ", last:" + last + ")";
}
}
...@Immutable...
public final class Person {private final String first;private final String last;
public String getFirst() {return first;
}
public String getLast() {return last;
}
@Overridepublic int hashCode() {
final int prime = 31;int result = 1;result = prime * result + ((first == null)
? 0 : first.hashCode());result = prime * result + ((last == null)
? 0 : last.hashCode());return result;
}
public Person(String first, String last) {this.first = first;this.last = last;
}// ...
// ...@Overridepublic boolean equals(Object obj) {
if (this == obj)return true;
if (obj == null)return false;
if (getClass() != obj.getClass())return false;
Person other = (Person) obj;if (first == null) {
if (other.first != null)return false;
} else if (!first.equals(other.first))return false;
if (last == null) {if (other.last != null)
return false;} else if (!last.equals(other.last))
return false;return true;
}
@Overridepublic String toString() {
return "Person(first:" + first+ ", last:" + last + ")";
}
}
boilerplate• Java Immutable Class
• As per Joshua Bloch Effective Java
...@Immutable
@Immutable class Person {String first, last
}
Awesome
With Macros (Groovy 2.5+)
import org.codehaus.groovy.ast.*
import org.codehaus.groovy.ast.stmt.*
import org.codehaus.groovy.ast.expr.*
def ast = new ReturnStatement(
new ConstructorCallExpression(
ClassHelper.make(Date),
ArgumentListExpression.EMPTY_ARGUMENTS
)
)
def ast = macro {return new Date()
}
With Macros (Groovy 2.5+)
Awesome
import org.codehaus.groovy.ast.*
import org.codehaus.groovy.ast.stmt.*
import org.codehaus.groovy.ast.expr.*
def ast = new ReturnStatement(
new ConstructorCallExpression(
ClassHelper.make(Date),
ArgumentListExpression.EMPTY_ARGUMENTS
)
)
def ast = macro {return new Date()
}
With Macros (Groovy 2.5+)
• Variations:
• Expressions, Statements, Classes
• Supports variable substitution,
specifying compilation phase
def ast = macro {return new Date()
}
With Macros (Groovy 2.5+)
• AST Matching:
• Selective transformations, filtering, testing
• Supports placeholders
Expression transform(Expression exp) {Expression ref = macro { 1 + 1 }
if (ASTMatcher.matches(ref, exp)) {return macro { 2 }
}
return super.transform(exp)}
Macro method examples
class X {
String name
}
class Y {
List<X> list
}
class Z {
Y y
}
Adapted from https://github.com/touchez-du-bois/akatsuki
but adapted for the experimental Antlr4 “Parrot” parser.
def getName(Z z) {
z.y.list[0].name
}
• nullSafe
Macro method examples
class X {
String name
}
class Y {
List<X> list
}
class Z {
Y y
}
def getName(Z z) {
z.y.list[0].name
}
Adapted from https://github.com/touchez-du-bois/akatsuki
but adapted for the experimental Antlr4 “Parrot” parser.• nullSafe
Macro method examples
class X {
String name
}
class Y {
List<X> list
}
class Z {
Y y
}
def getName(Z z) {
z.y.list[0].name
}
Prone to NPE
Adapted from https://github.com/touchez-du-bois/akatsuki
but adapted for the experimental Antlr4 “Parrot” parser.• nullSafe
Macro method examples
• nullSafe
class X {
String name
}
class Y {
List<X> list
}
class Z {
Y y
}
def getName(Z z) {
def result = null
if (z != null && z.y != null &&
z.y.list != null && z.y.list[0] != null) {
result = z.y.list[0].name
}
result
}
Adapted from https://github.com/touchez-du-bois/akatsuki
but adapted for the experimental Antlr4 “Parrot” parser.
Macro method examples
class X {
String name
}
class Y {
List<X> list
}
class Z {
Y y
}
def getName(Z z) {
def result = null
if (z != null && z.y != null &&
z.y.list != null && z.y.list[0] != null) {
result = z.y.list[0].name
}
result
}
Verbose
Adapted from https://github.com/touchez-du-bois/akatsuki
but adapted for the experimental Antlr4 “Parrot” parser.• nullSafe
Macro method examples
def getName(Z z) {
z?.y?.list?[0]?.name
}
class X {
String name
}
class Y {
List<X> list
}
class Z {
Y y
}
Adapted from https://github.com/touchez-du-bois/akatsuki
but adapted for the experimental Antlr4 “Parrot” parser.• nullSafe
class X {
String name
}
class Y {
List<X> list
}
class Z {
Y y
}
Macro method examples
Adapted from https://github.com/touchez-du-bois/akatsuki
but adapted for the experimental Antlr4 “Parrot” parser.• nullSafe
def getName(Z z) {
nullSafe(z.y.list[0].name)
}
Macro method examples
@Macrostatic Expression nullSafe(MacroContext macroContext, Expression expression) {
if (expression instanceof PropertyExpression) {// exp.prop -> exp?.prop …
} else if (expression instanceof MethodCallExpression) {// exp.method() -> exp?.method() …
}return expression
}
• nullSafe
def getName(Z z) {
nullSafe(z.y.list[0].name)
}
Macro method examples
def fact(num) {return match(num) {
when String then fact(num.toInteger())when(0 | 1) then 1when 2 then 2orElse num * fact(num - 1)
}}
assert fact("5") == 120
See: https://github.com/touchez-du-bois/akatsuki
Macro method examples• Spock inspired
@Grab('org.spockframework:spock-core:1.0-groovy-2.4')import spock.lang.Specification
class MathSpec extends Specification {def "maximum of two numbers"(int a, int b, int c) {
expect:Math.max(a, b) == c
where:a | b | c1 | 3 | 37 | 4 | 70 | 0 | 0
}}
Macro method examplesSee: https://github.com/touchez-du-bois/akatsuki
doWithData {dowith:
assert a + b == cwhere:
a | b || c1 | 2 || 34 | 5 || 97 | 8 || 15
}
What makes Groovy Awesome?
• Java integration
• Scripting support
• Multiparadigm
• Gradual typing
• Metaprogramming
• Runtime
• Compile-time
• Macros
• Domain Specific Language support
• Ecosystem
• Community/Team
• Ability to change the language
at runtime
• Ability to change the language
during the compilation process
• Macros provide a
homogeneous form
for writing AST
transformations
Awesome
Part 2: What makes Groovy Awesome?
• Java integration
• Scripting support
• Multiparadigm
• Gradual typing
• Metaprogramming
• Domain Specific Language support
Command chains
• Cranes and tortoises revisited
• Ecosystem
• Community/Team
Command Chains
• Ability to chain method calls without parentheses and dots
Awesome
Command Chains
• Ability to chain method calls without parentheses and dots
move forward at 3.km/h
Command Chains
• Ability to chain method calls without parentheses and dots
• Equivalent to:
move forward at 3.km/h
move(forward).at(3.getKm().div(h))
Command chains in DSLs
Number.metaClass.getShares = { delegate }
Number.metaClass.getDollars = { delegate }
String GOOG = 'Google'
def sell(int nShares) {
[of: { String ticker ->
[at: { int price ->
println "Sold $nShares $ticker at \$$price"
}]
}]
}
sell 100.shares of GOOG at 1000.dollars
Command chains in DSLs
show = { println it }square_root = { Math.sqrt(it) }
def please(action) {[the: { what ->
[of: { n -> action(what(n)) }]}]
}
please show the square_root of 100// ==> 10.0
Command chains in DSLs
show = { println it }square_root = { Math.sqrt(it) }
def please(action) {[the: { what ->
[of: { n -> action(what(n)) }]}]
}
please(show).the(square_root).of(100)// ==> 10.0
Command chains in DSLs
show = { println it }square_root = { Math.sqrt(it) }
def please(action) {[the: { what ->
[of: { n -> action(what(n)) }]}]
}
please(show).the(square_root).of(100)// ==> 10.0
… and again in another language …
Command chains in DSLs
// Japanese DSLObject.metaClass.を =Object.metaClass.の ={ clos -> clos(delegate) }
まず = { it }表示する = { println it }平方根 = { Math.sqrt(it) }
まず 100 の 平方根 を 表示する// First, show the square root of 100
// => 10.0 // source: http://d.hatena.ne.jp/uehaj/20100919/1284906117
// http://groovyconsole.appspot.com/edit/241001
Part 3: What makes Groovy Awesome?
• Java integration
• Scripting support
• Multiparadigm
• Gradual typing
• Metaprogramming
• Domain Specific Language support
• Command chains
Cranes and tortoises revisited
• Ecosystem
• Community/Team
Logic programming example
cranes have 2 legstortoises have 4 legs
there are 7 animalsthere are 20 legs
How many of each animal?
Recall: Constraint programming example
@Grab('org.choco-solver:choco-solver:4.0.0')import org.chocosolver.solver.Model
def m = new Model()
def totalAnimals = 7def totalLegs = 20def numCranes = m.intVar('Cranes', 0, totalAnimals, true)def numTortoises = m.intVar('Tortoises', 0, totalAnimals, true)def numCraneLegs = m.intScaleView(numCranes, 2)def numTortoiseLegs = m.intScaleView(numTortoises, 4)m.arithm(numCranes, '+', numTortoises, '=', totalAnimals).post()m.arithm(numCraneLegs, '+', numTortoiseLegs, '=', totalLegs).post()if (m.solver.solve())println "$numCranes\n$numTortoises"
elseprintln "No Solutions" Cranes = 4
Tortoises = 3
ChocoCraneTortoise.groovy
Logic programming exampledslUntyped/ChocoCraneTortoiseDSL.groovy50 lines to define the DSL
Logic programming example
cranes have 2 legstortoises have 4 legs
there are 7 animalsthere are 20 legs
display solution
Cranes 4
Tortoises 3
dslUntyped/ChocoCraneTortoiseDSL.groovy50 lines to define the DSL
Logic programming example
cranes have 2 legstortoises have 4 legsmillipedes have 1000 legsthere are 8 animalsthere are 1020 legs
display solution
Cranes 4
Tortoises 3
Millipedes 1
dslUntyped/ChocoCraneTortoiseDSL.groovy50 lines to define the DSL
Logic programming example
Crane(2), Tortoise(4), Beetle(6), Centipede(100), Millipede(1000)
Solution: Tortoise = 3, Beetle = 23
Solution: Crane = 1, Tortoise = 1, Beetle = 24
Solution: Crane = 25, Centipede = 1
dslTyped/ChocoCraneTortoiseDSL.groovy80 lines to define the DSL
@TypeCheckeddef main() {animals seen include Crane, Tortoise, Beetle, Centipedeleg count is 150head count is 26display solution
}
@TypeCheckeddef main() {animals seen include Crane, Tortoise, Beetle, Centipedeleg count is 150head count is 26display solution
}
Logic programming exampledslTyped/ChocoCraneTortoiseDSL.groovy80 lines to define the DSL
DSL Type Provider
unresolvedVariable { var ->
if (!cachedAnimalNames) {
def accessKey = '72ddf45a-c751-44c7-9bca-8db3b4513347'
// for illustrative purposes, just download xml for a few animals
def uid = 'ELEMENT_GLOBAL.2.104550,ELEMENT_GLOBAL.2.105196,ELEMENT_GLOBAL.2.120227'
def base = "https://services.natureserve.org/idd/rest/ns/v1.1/globalSpecies"
def url = "$base/comprehensive?uid=$uid&NSAccessKeyId=$accessKey"
def root = new XmlParser().parse(url)
def names = root.globalSpecies.classification.names
cachedAnimalNames = names.natureServePrimaryGlobalCommonName*.text()*.replaceAll(' ','')
}
if (var.name in cachedAnimalNames) {
storeType(var, STRING_TYPE)
handled = true
enclosingClassNode.addField(var.name, 0, STRING_TYPE, new ConstantExpression(var.name))
}
}
DSL Type Provider
@TypeChecked(extensions='NatureServeAnimalProvider.groovy')
def main() {
animals seen include SandhillCrane, GopherTortoise, ChihuahuanMillipede
leg count is 1020
head count is 8
display solution
}
Custom checkerprovider/ChocoCraneTortoiseProvider.groovy80 lines to define the DSL25 lines to define the provider
Awesome
AwesomeAwesome
Awesome
Ecosystem
Groovy's Awesome Team
Groovy's Awesome Team
Groovy's Awesome Team
+100's of others
Direct downloads:
2013: approx. 3 million
2014: 4+ million
2015: 12+ million
now: 2+ million/month
Groovy's Awesome Team
+100's of others just like YOU!
http://www.manning.com/koenig2
Groovy in Action 2nd edition
Manning promo code:
ctwg3sum
(39% off all Manning books)
152