groovy-dsl-talk



groovy-dsl-talk

0 0


groovy-dsl-talk

Lightning talk on creating DSLs in Groovy

On Github thecodesmith / groovy-dsl-talk

DSLs in Groovy

A lightning talk

Stories in Code

Master programmers think of systems as stories to be told rather than programs to be written.

-- Robert C. Martin: Clean Code

Domain-Specific Languages

Write code in the language of the problem domain

Groovy Language

The TRUE Java scripting language

  • Integrates seamlessly with Java - compiled to Java bytecode
  • Dynamic, with static typing available
  • Java-flavored syntax without all the boilerplate
  • Powerful methods added to augment the JDK
  • Functional flavor - functions are first-class citizens

Syntactic Sugar

Groovy's flexible & malleable syntax

  • Script files: no need to write full-blown classes
  • Optional typing (can even omit "def" keyword in scripts)
  • Native syntax constructs (lists, maps, etc)
  • Parentheses and semi-colons are optional
  • BigDecimal by default for decimal numbers
  • Closures for custom control structures
  • Operator overloading

Scripts vs Classes

Java:

public class Action {
    public static void main(String[] args) {
        System.out.println("Hello");
    }
}

Groovy:

println "Hello"

Optional Typing

Java

Rate<LoanType, Duration, BigDecimal>[] table() { ... }

Groovy

def table() { ... }

Native Constructs

Lists

def days = [Monday, Tuesday, Wednesday]

Maps

def states = [WI: 'Wisconsin', TX: 'Texas']

Ranges

def weekdays = Monday..Friday
def allowedAge = 18..65

Optional Parens & Semicolons

Java

move(left);

Groovy

move left

BigDecimal by Default

Example: Simple interpolation equation

Java

BigDecimal uMinusV = c.subtract(a);
BigDecimal vMinusL = b.subtract(c);
BigDecimal uMinusL = a.subtract(b);
return e.multiply(uMinusV)
        .add(d.multiply(vMinusL))
        .divide(uMinusL, 10, BigDecimal.ROUND_HALF_UP);

Groovy

(d * (b - c) + e * (c - a)) / (a - b)

Closures

When closures are last parameter, they can be outside parentheses

Example: Custom control structures

// method taking a closure:
def unless(Boolean condition, Closure action) {
    log.info "Executing action" 
    action()
}

// usage:
unless (account.balance < 100.euros) {
    account.debit 100.euros
}

Operator Overloading

a + b   // a.plus(b)
a - b   // a.minus(b)
a * b   // a.multiply(b)
a / b   // a.divide(b)
a % b   // a.modulo(b)
a ** b  // a.power(b)
a & b   // a.and(b)
a ^ b   // a.xor(b)
a[b]    // a.getAt(b)
a << b  // a.leftShift(b)
a >> b  // a.rightShift(b)
+a      // a.positive()
-a      // a.negative()
~a      // a.bitwiseNegate()

Operator Overloading

Usages

Currency

15.euros + 10.dollars

Distances

10.miles * 3 == 30.miles

Workflow and Concurrency

taskA | taskB & taskC

Credit an account

account << 10.dollars
account += 10.dollars
account.credit 10.dollars

Property Access

Methods of the form getXyz() can be accessed like properties

class Person {
    String firstName
    String lastName

    String getFullName() {
        return "$firstName $lastName"
    }
}

person = new Person(firstName: 'Brian', lastName: 'Stewart')

// Accessed like a property, but calls getFullName() under the hood:
println person.fullName

The Groovy MarkupBuilder

Other builders: XMLBuilder, JSONBuilder, SwingBuilder, etc.

def mkp = new MarkupBuilder()
mkp.html {
    head {
        title "Groovy in Action"
    }
    body {
        div(width: "100") {
            p(class: "para") {
                span "Table of Contents"
            }
        }
    }
}

SwingBuilder

count = 0
new SwingBuilder().edt {
    frame(title: 'Counter', size: [300, 300], show: true) {
        borderLayout()

        textlabel = label(text: 'Click the button!', constraints: BL.NORTH)

        button(
                text: 'Click Me',   
                constraints: BL.SOUTH,
                actionPerformed: { 
                    count++
                    textlabel.text = "Clicked ${count} time(s)." 
                }
        )
    }
}

Chaining Commands

// equivalent to: turn(left).then(right)
turn left then right

// equivalent to: take(2.pills).of(chloroquinine).after(6.hours)
take 2.pills of chloroquinine after 6.hours

// equivalent to: paint(wall).with(red, green).and(yellow)
paint wall with red, green and yellow

// with named parameters too
// equivalent to: check(that: car).is(stopped)
check that: car is stopped

// with closures as parameters
// equivalent to: given({}).when({}).then({})
given { } when { } then { }

Spock Testing Framework

import spock.lang.Specification

class AccountSpec extends Specification {

    def "crediting empty account results in correct balance"() {

        given: "an empty bank account"
        def account = new Account(balance: 0)

        when: "the account is credited $10"
        account << 10.dollars

        then: "the account's balance is $10"
        account.balance == 10.dollars
    }
}

How is it done?

ExpandoMetaClass

Add methods to the metaClass of current classes (even JDK classes!)

// create a closure called getMeters in the Number meta-class
Number.metaClass.getMeters = {
    new Distance(delegate, Unit.METERS)
}

// usage:
100.meters

AST Transformations

Easy to write custom AST transformations

Then applied with annotations

Example: @Canonical AST transform built into Groovy

@Canonical
class Person {
    String firstName
    String lastName
}

// adds constructors with parameters matching fields
user = new Person('Brian', 'Stewart') 

// generates toString() method
println user  // output: Person(Brian, Stewart)

Advanced Methods

There are many advanced methods to create sophisticated, highly readable and maintainable domain-specific languages in Groovy, including:

  • Script inheritance
  • Variable injection
  • Compiler customizations
  • Many others

Questions?

Thank You!