awesome groovy

151
Dr Paul King Groovy 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

Upload: paul-king

Post on 12-Jul-2015

4.264 views

Category:

Software


0 download

TRANSCRIPT

Page 1: awesome groovy

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

Page 2: awesome groovy
Page 3: awesome groovy

Part 1: Introducing Groovy?

Page 4: awesome 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.”

Page 5: awesome groovy
Page 6: awesome groovy

Why Groovy?

Java

Page 7: awesome groovy

Why Groovy?

Too verboseSupport

functional

programming

Better

concurrency

Evolves

too slowly

I need

feature

XXX

Too complex

for some

situations

More

flexibility

Java

Page 8: awesome groovy

Why Groovy?

Groovy

Make it

simplerMake it

dynamic

Support

simple

scripting

Page 9: awesome groovy

Why Groovy?

Groovy

Make it

simplerMake it

dynamic

Support

simple

scripting

Java

integration

Page 10: awesome groovy

Why Groovy?

Groovy

Make it

simplerMake it

dynamic

Support

simple

scripting

Good IDE

support

Java

integration

Page 11: awesome groovy

Why Groovy?

Groovy

Make it

simplerMake it

dynamic

Support

simple

scripting

Good IDE

support

Custom

features

Java

integration

Page 12: awesome groovy

Why Groovy?

Groovy

Make it

simplerMake it

dynamic

Support

simple

scripting

Support

concurrency

Good IDE

support

Custom

features

Java

integration

Page 13: awesome groovy

Why Groovy?

Groovy

Make it

simplerMake it

dynamic

Support

simple

scripting

Support

functional

style

Support

concurrency

Good IDE

support

Custom

features

Java

integration

Page 14: awesome groovy
Page 15: awesome groovy

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);

}}

}

Page 16: awesome groovy

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

Page 17: awesome 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?

Page 18: awesome groovy

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)}

Page 19: awesome groovy

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?

Page 20: awesome groovy

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:

Page 21: awesome groovy

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

Page 22: awesome groovy

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 }

Page 23: awesome groovy

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

Page 24: awesome groovy

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

Page 25: awesome groovy

Or typed Groovy DSL version if required

Page 26: awesome groovy

Groovy DSL being debugged

Page 27: awesome groovy

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

}

Page 28: awesome groovy

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)}

}}

}

Page 29: awesome groovy

@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)}

}}

}

Page 30: awesome groovy

@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

Page 31: awesome groovy

@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

Page 32: awesome groovy

What style of language is Groovy?

Page 33: awesome groovy

Groovy Style

• Imperative/OO but somewhat

paradigm agnostic

• Dynamic, optionally static (gradual

extensible typing)

• Extensible language features

through metaprogramming

Page 34: awesome groovy

Part 2: What makes Groovy Awesome?

Page 35: awesome groovy

What makes Groovy Awesome?

• Java integration

• Scripting support

• Multiparadigm

• Gradual typing

• Metaprogramming

• Domain specific

language (DSL) support

• Ecosystem

• Community/Team

Awesome

Page 36: awesome groovy

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

Page 37: awesome groovy

Java Integration

• Groovy is Java’s friend

Java

Groovy

Page 38: awesome 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

Page 39: awesome 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

Page 40: awesome 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

Page 41: awesome 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)

Page 42: awesome groovy

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

Page 43: awesome groovy

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

Page 44: awesome groovy

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

Page 45: awesome groovy

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

Page 46: awesome groovy

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

Page 47: awesome groovy

Scripting

• Terse format

• Isolation

• Domain-Specific

Languages (DSLs)

• Security

• Flexibility

Page 48: awesome groovy

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"

Page 49: awesome groovy

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

Page 50: awesome groovy

Scripting

Page 51: awesome groovy

Scripting

Page 52: awesome groovy

Scripting

Page 53: awesome groovy

Scripting

class Robot {void move(dir) {println "robot moved $dir"

}}

def robot = new Robot()

Page 54: awesome groovy

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

Page 55: awesome groovy

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

Page 56: awesome groovy

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)

Page 57: awesome groovy

Scripting

move left

move forward at 3.km/h

More

examples

later

Page 58: awesome groovy

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

Page 59: awesome groovy

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

Page 60: awesome groovy

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

Page 61: awesome groovy

Multiparadigm

Imperative/OO

Logic programming

Functional

Page 62: awesome groovy

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

Page 63: awesome groovy

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!"

Page 64: awesome groovy

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

Page 65: awesome groovy

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'

Page 66: awesome groovy

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'

Page 67: awesome groovy

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'

Page 68: awesome groovy

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

Page 69: awesome groovy

Logic programming example

cranes have 2 legstortoises have 4 legs

there are 7 animalsthere are 20 legs

How many of each animal?

Page 70: awesome groovy

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

Page 71: awesome groovy

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

Page 72: awesome groovy

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

Page 73: awesome groovy

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

Page 74: awesome groovy

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

Page 75: awesome groovy

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

Page 76: awesome groovy

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

Page 77: awesome groovy

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

Page 78: awesome groovy

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

Page 79: awesome groovy

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

Page 80: awesome groovy

Gradual typing

• Dynamic by default

• Gradual typing

• Static type checking

• Extensible type system

Awesome

Page 81: awesome groovy

Dynamic vs static

82

def myPets = ['Lassie', 'Skip']

List<String> yourPets = ['Lassie', 'Skip']

shouldFail(ClassCastException) {

List ourPets = new Date()

}

Page 82: awesome groovy

Dynamic vs static

83

Gradual

Type-time?

def myPets = ['Lassie', 'Skip']

List<String> yourPets = ['Lassie', 'Skip']

shouldFail(ClassCastException) {

List ourPets = new Date()

}

Page 83: awesome groovy

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'

Page 84: awesome groovy

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

Page 85: awesome groovy

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()

}

'''

}

Page 86: awesome groovy

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

Page 87: awesome groovy

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.

Page 88: awesome groovy

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

Page 89: awesome groovy

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()

Page 90: awesome groovy

Static compilation speed

0

5

10

15

20

25

30

Fibonacci micro-benchmark

Page 91: awesome groovy

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

Page 92: awesome groovy

What makes Groovy Awesome?

• Java integration

• Scripting support

• Multiparadigm

• Gradual typing

• Metaprogramming

• Runtime

• Compile-time

• Macros

• Domain Specific Language support

• Ecosystem

• Community/Team

Page 93: awesome groovy

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

Page 94: awesome groovy

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'

Page 95: awesome groovy

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])

Page 96: awesome groovy

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'

Page 97: awesome groovy

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

Page 98: awesome groovy

Compile-time metaprogramming

• Gives you the ability to change the language by augmenting the

compilation processAwesome

Page 99: awesome groovy

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)

Page 100: awesome groovy

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)

Page 101: awesome groovy

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

Page 102: awesome groovy

• 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

Page 103: awesome groovy

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)

Page 104: awesome groovy

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…

Page 105: awesome groovy

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

...

Page 106: awesome groovy

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

Page 107: awesome groovy

@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 + ")";

}

}

Page 108: awesome groovy

...@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

Page 109: awesome groovy

...@Immutable

@Immutable class Person {String first, last

}

Awesome

Page 110: awesome groovy

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()

}

Page 111: awesome groovy

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()

}

Page 112: awesome groovy

With Macros (Groovy 2.5+)

• Variations:

• Expressions, Statements, Classes

• Supports variable substitution,

specifying compilation phase

def ast = macro {return new Date()

}

Page 113: awesome groovy

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)}

Page 114: awesome groovy

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

Page 115: awesome groovy

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

Page 116: awesome groovy

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

Page 117: awesome groovy

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.

Page 118: awesome groovy

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

Page 119: awesome groovy

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

Page 120: awesome groovy

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)

}

Page 121: awesome groovy

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)

}

Page 122: awesome groovy

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

Page 123: awesome groovy

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

}}

Page 124: awesome groovy

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

}

Page 125: awesome groovy

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

Page 126: awesome groovy

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

Page 127: awesome groovy

Command Chains

• Ability to chain method calls without parentheses and dots

Awesome

Page 128: awesome groovy

Command Chains

• Ability to chain method calls without parentheses and dots

move forward at 3.km/h

Page 129: awesome groovy

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))

Page 130: awesome groovy

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

Page 131: awesome groovy

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

Page 132: awesome groovy

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

Page 133: awesome groovy

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 …

Page 134: awesome groovy

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

Page 135: awesome groovy

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

Page 136: awesome groovy

Logic programming example

cranes have 2 legstortoises have 4 legs

there are 7 animalsthere are 20 legs

How many of each animal?

Page 137: awesome groovy

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

Page 138: awesome groovy

Logic programming exampledslUntyped/ChocoCraneTortoiseDSL.groovy50 lines to define the DSL

Page 139: awesome groovy

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

Page 140: awesome groovy

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

Page 141: awesome groovy

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

}

Page 142: awesome groovy

@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

Page 143: awesome groovy

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))

}

}

Page 144: awesome groovy

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

Page 145: awesome groovy

Awesome

AwesomeAwesome

Awesome

Ecosystem

Page 146: awesome groovy

Groovy's Awesome Team

Page 147: awesome groovy

Groovy's Awesome Team

Page 148: awesome groovy

Groovy's Awesome Team

+100's of others

Direct downloads:

2013: approx. 3 million

2014: 4+ million

2015: 12+ million

now: 2+ million/month

Page 149: awesome groovy

Groovy's Awesome Team

+100's of others just like YOU!

Page 150: awesome groovy
Page 151: awesome groovy

http://www.manning.com/koenig2

Groovy in Action 2nd edition

Manning promo code:

ctwg3sum

(39% off all Manning books)

152