leveraging groovy for capturing business rules

91
© ASERT 2006-2013 Dr Paul King @paulk_asert http:/slideshare.net/paulk_asert/groovy-rules https://github.com/paulk-asert/groovy-rules Leveraging Groovy for Capturing Business Rules

Upload: spring-io

Post on 01-Nov-2014

489 views

Category:

Technology


6 download

DESCRIPTION

Speaker: Paul King Groovy has excellent support for the creation of Domain Specific Languages (DSLs). Such DSLs can be particularly useful when writing business rules. Rules can be written in English-like phrases which are straight-forward to read or write (by non-developers) yet can be fully executable code corresponding to a layer over the top of a traditional logic solving API. This talk illustrates various DSLs, highlights several logic solving APIs and looks at the pros and cons of the various approaches (including tool support, flexibility, lock-in). Whilst Groovy is the language of choice for this talk, the techniques and principles are not specific to Groovy and apply readily to your favourite modern scripting language. The "logic solving" APIs being highlighted are primarily Choco, Drools Expert and Drools Planner but again these are just illustrative of the logic APIs that you can use when writing a DSL layer. We look at the benefits and costs when writing such DSL layers, numerous real-world examples and the all-important aspects of tooling; covering what non-developer, developer and cloud tooling is available with this kind of approach. To give a flavour of the talk, here is a snippet from one of the code examples (Einstein’s riddle): the Briton has a red house the owner of the green house drinks coffee the owner of the yellow house plays baseball the person known to play football keeps birds the man known to play tennis drinks beer the green house is on the left side of the white house the man known to play volleyball lives next to the one who keeps cats the Norwegian lives next to the blue house When discussing this example, we look at how you create and debug such code, illustrate how several APIs can be used underneath this DSL layer, discuss the costs involved in creating the above DSL in its basic form and in more complex forms that allow type checking, code completion etc. and options for parallelism and cloud deployment.

TRANSCRIPT

Page 1: Leveraging Groovy for Capturing Business Rules

© A

SE

RT

2006-2

013

Dr Paul King

@paulk_asert

http:/slideshare.net/paulk_asert/groovy-rules

https://github.com/paulk-asert/groovy-rules

Leveraging Groovy for

Capturing Business Rules

Page 2: Leveraging Groovy for Capturing Business Rules

Topics

Introduction to DSLs

• Introduction to Groovy

• DSLs in Groovy

• Why Groovy?

• Tortoise & Crane Example

• Einstein’s Riddle

• Further Discussion

• More Info

© A

SE

RT

2006-2

013

Page 3: Leveraging Groovy for Capturing Business Rules

What is a DSL?

• A domain-specific language is a

programming language or executable

specification language that offers, through

appropriate notations and abstractions,

expressive power focused on, and usually

restricted to, a particular problem domain

– declarative data << DSL << general-purpose programming

language (GPL)

– AKA: fluent / human interfaces, language oriented

programming, problem-oriented languages, little / mini

languages, macros, business natural languages Sources:

http://en.wikipedia.org/wiki/Domain-specific_language

van Deursen, A., Klint, P., Visser, J.: Domain-specific languages: an annotated bibliography. ACM SIGPLAN Notices 35 (2000) 26–36

© A

SE

RT

2006-2

013

Page 4: Leveraging Groovy for Capturing Business Rules

Goals of DSLs

• Use a more expressive language than a

general-purpose one

• Share a common metaphor of

understanding between developers and

subject matter experts

• Have domain experts help with the design

of the business logic of an application

• Avoid cluttering business code with

boilerplate technical code thanks to a

clean separation

• Let business rules have their own lifecycle

Page 5: Leveraging Groovy for Capturing Business Rules

Why Scripting DSLs package org.drools.examples.golfing; dialect "mvel" import org.drools.examples.golfing.GolfingExample.Golfer; rule "find solution" when // Bob is wearing plaid pants

$bob : Golfer( name == "Bob", color == "plaid") // ... then

System.out.println( "Bob " + $bob.getColor() ); end

package org.drools.examples.golfing; dialect "mvel" import org.drools.examples.golfing.GolfingExample.Golfer; rule "find solution" when

Bob is wearing plaid pants // ... then

Display all details end

when Bob is wearing plaid pants display all details

Compile time

translation

Compile time

or runtime

translation

Page 6: Leveraging Groovy for Capturing Business Rules

Why a DSL?

• Advantages:

– Domain experts can

understand, validate,

modify, and often even

develop DSL programs

– Self-documenting (?)

– Enhance quality,

productivity, reliability,

maintainability, portability

and reusability

– Safety; language

constructs can be made

safe

– Tooling

• Disadvantages:

– Learning cost vs. limited

applicability

– Cost of designing,

implementing &

maintaining DSL & tools

– Attaining proper scope

– Trade-offs between DSL

specific and general-

purpose programming

language constructs

– Efficiency costs

– Proliferation of similar

non-standard DSLs

– Tooling

© A

SE

RT

2006-2

013

Page 7: Leveraging Groovy for Capturing Business Rules

Topics

• Introduction to DSLs

Introduction to Groovy

• DSLs in Groovy

• Why Groovy?

• Tortoise & Crane Example

• Einstein’s Riddle

• Further Discussion

• More Info

© A

SE

RT

2006-2

013

Page 8: Leveraging Groovy for Capturing Business Rules

Java code for list manipulation

© A

SE

RT

2006-2

013

import 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 main = new Main(); List shortNames = main.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); } } }

Based on an

example by

Jim Weirich

& Ted Leung

Page 9: Leveraging Groovy for Capturing Business Rules

Groovy code for list manipulation

© A

SE

RT

2006-2

013

import 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 main = new Main(); List shortNames = main.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.java

to Main.groovy

Page 10: Leveraging Groovy for Capturing Business Rules

Some Java Boilerplate identified

© A

SE

RT

2006-2

013

import 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 main = new Main(); List shortNames = main.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 11: Leveraging Groovy for Capturing Business Rules

Java Boilerplate removed

© A

SE

RT

2006-2

013

def 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 12: Leveraging Groovy for Capturing Business Rules

More Java Boilerplate identified

© A

SE

RT

2006-2

013

def 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 13: Leveraging Groovy for Capturing Business Rules

Boilerplate removed = nicer Groovy version

© A

SE

RT

2006-2

013

names = ["Ted", "Fred", "Jed", "Ned"] println names shortNames = names.findAll{ it.size() < 4 } println shortNames.size() shortNames.each{ println it }

["Ted", "Fred", "Jed", "Ned"] 3 Ted Jed Ned

Output:

Page 14: Leveraging Groovy for Capturing Business Rules

Or Groovy DSL version if required

© A

SE

RT

2006-2

013

names = [] def of, having, less = null def 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 names } ] def display(arg) { arg }

given the names "Ted", "Fred", "Jed" and "Ned" display all the names display the number of names having size less than 4 display the names having size less than 4

Page 15: Leveraging Groovy for Capturing Business Rules

Closures

© A

SE

RT

2006-2

013

int twice(int arg) { arg * 2 } def triple = { int arg -> arg * 3 } println twice(3) // => 6 println triple(3) // => 9

Page 16: Leveraging Groovy for Capturing Business Rules

Grapes / Grab: Google collections

© A

SE

RT

2006-2

013

@Grab('com.google.guava:guava:r09') import com.google.common.collect.HashBiMap HashBiMap fruit = [grape:'purple', lemon:'yellow', lime:'green'] assert fruit.lemon == 'yellow' assert fruit.inverse().yellow == 'lemon'

Page 17: Leveraging Groovy for Capturing Business Rules

© A

SE

RT

2006-2

011

Groovy Builders

<html> <head> <title>Hello</title> </head> <body> <ul> <li>world 1</li> <li>world 2</li> <li>world 3</li> <li>world 4</li> <li>world 5</li> </ul> </body> </html>

import groovy.xml.* def page = new MarkupBuilder() page.html { head { title 'Hello' } body { ul { for (count in 1..5) { li "world $count" } } } }

• Markup Builder

Page 18: Leveraging Groovy for Capturing Business Rules

Topics

• Introduction to DSLs

• Introduction to Groovy

DSLs in Groovy

• Why Groovy?

• Tortoise & Crane Example

• Einstein’s Riddle

• Further Discussion

• More Info

© A

SE

RT

2006-2

013

Page 19: Leveraging Groovy for Capturing Business Rules

DSL example...

© A

SE

RT

2006-2

013

class FluentApi { def action, what def the(what) { this.what = what; this } def of(arg) { action(what(arg)) } } show = { arg -> println arg } square_root = { Math.sqrt(it) } please = { new FluentApi(action: it) } please show the square_root of 100 // => 10.0

Page 20: Leveraging Groovy for Capturing Business Rules

…DSL example...

© A

SE

RT

2006-2

013

class FluentApi { def action, what def the(what) { this.what = what; this } def of(arg) { action(what(arg)) } } show = { arg -> println arg } square_root = { Math.sqrt(it) } please = { new FluentApi(action: it) } please show the square_root of 100 // => 10.0

DSL implementation details

Page 21: Leveraging Groovy for Capturing Business Rules

…DSL example...

© A

SE

RT

2006-2

013

class FluentApi { def action, what def the(what) { this.what = what; this } def of(arg) { action(what(arg)) } } show = { arg -> println arg } square_root = { Math.sqrt(it) } please = { new FluentApi(action: it) } please show the square_root of 100 // => 10.0

DSL usage

Page 22: Leveraging Groovy for Capturing Business Rules

…DSL example...

© A

SE

RT

2006-2

013

please show the square_root of 100

Page 23: Leveraging Groovy for Capturing Business Rules

…DSL example...

© A

SE

RT

2006-2

013

please show the square_root of 100

please(show).the(square_root).of(100)

Page 24: Leveraging Groovy for Capturing Business Rules

…DSL example...

© A

SE

RT

2006-2

013

class FluentApi { def action, what def the(what) { this.what = what; this } def of(arg) { action(what(arg)) } } show = { arg -> println arg } square_root = { Math.sqrt(it) } please = { new FluentApi(action: it) } please(show).the(square_root).of(100)

Page 25: Leveraging Groovy for Capturing Business Rules

…DSL example...

© A

SE

RT

2006-2

013

Object.metaClass.please = { clos -> clos(delegate) } Object.metaClass.the = { clos -> delegate[1](clos(delegate[0])) } show = { thing -> [thing, { println it }] } square_root = { Math.sqrt(it) } given = { it } given 100 please show the square_root // ==> 10.0

Page 26: Leveraging Groovy for Capturing Business Rules

...DSL example...

© A

SE

RT

2006-2

013

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 27: Leveraging Groovy for Capturing Business Rules

...DSL example...

© A

SE

RT

2006-2

013

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

Inspiration for this example came from …

Page 28: Leveraging Groovy for Capturing Business Rules

...DSL example

© A

SE

RT

2006-2

013

// Japanese DSL using GEP3 rules Object.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

also: http://groovyconsole.appspot.com/edit/241001

Page 29: Leveraging Groovy for Capturing Business Rules

Topics

• Introduction to DSLs

• Introduction to Groovy

• DSLs in Groovy

Why Groovy?

• Tortoise & Crane Example

• Einstein’s Riddle

• Further Discussion

• More Info

© A

SE

RT

2006-2

013

Page 30: Leveraging Groovy for Capturing Business Rules

Groovy provides

• A flexible and malleable syntax – scripts, native syntax constructs (list, map,

ranges)

• Closures, less punctuation... – Compile-time and runtime meta-programming

– metaclasses, AST transformations

– also operator overloading

• The ability to easily integrate into Java,

app’n server apps – compile into bytecode or leave in source form

– also security and safety

Page 31: Leveraging Groovy for Capturing Business Rules

Compile-time Metaprogramming

© A

SE

RT

2006-2

013

Transformation

Page 32: Leveraging Groovy for Capturing Business Rules

© A

SE

RT

2006-2

013

Better Design Patterns: Delegate…

import java.util.Date; public class Event { private String title; private String url; private Date when; public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } // ...

public Date getWhen() { return when; }

public void setWhen(Date when) { this.when = when; }

public boolean before(Date other) { return when.before(other); }

public void setTime(long time) { when.setTime(time); }

public long getTime() { return when.getTime(); }

public boolean after(Date other) { return when.after(other); } // ...

Page 33: Leveraging Groovy for Capturing Business Rules

© A

SE

RT

2006-2

013

…Better Design Patterns: Delegate…

import java.util.Date; public class Event { private String title; private String url; private Date when; public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } // ...

public Date getWhen() { return when; }

public void setWhen(Date when) { this.when = when; }

public boolean before(Date other) { return when.before(other); }

public void setTime(long time) { when.setTime(time); }

public long getTime() { return when.getTime(); }

public boolean after(Date other) { return when.after(other); } // ...

boilerplate

Page 34: Leveraging Groovy for Capturing Business Rules

© A

SE

RT

2006-2

013

…Better Design Patterns: Delegate

class Event { String title, url @Delegate Date when }

Page 35: Leveraging Groovy for Capturing Business Rules

© A

SE

RT

2006-2

013

…Better Design Patterns: Delegate

class Event { String title, url @Delegate Date when }

def gr8conf = new Event(title: "GR8 Conference", url: "http://www.gr8conf.org", when: Date.parse("yyyy/MM/dd", "2009/05/18")) def javaOne = new Event(title: "JavaOne", url: "http://java.sun.com/javaone/", when: Date.parse("yyyy/MM/dd", "2009/06/02")) assert gr8conf.before(javaOne.when)

Page 36: Leveraging Groovy for Capturing Business Rules

@Immutable...

• Java Immutable Class – As per Joshua Bloch

Effective Java

© A

SE

RT

2006-2

013

public final class Person { private final String first; private final String last; public String getFirst() { return first; } public String getLast() { return last; } @Override public 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; } // ...

// ... @Override public 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; } @Override public String toString() { return "Person(first:" + first + ", last:" + last + ")"; } }

Page 37: Leveraging Groovy for Capturing Business Rules

...@Immutable...

• Java Immutable Class – As per Joshua Bloch

Effective Java

© A

SE

RT

2006-2

013

public final class Person { private final String first; private final String last; public String getFirst() { return first; } public String getLast() { return last; } @Override public 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; } // ...

// ... @Override public 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; } @Override public String toString() { return "Person(first:" + first + ", last:" + last + ")"; } }

boilerplate

Page 38: Leveraging Groovy for Capturing Business Rules

...@Immutable

© A

SE

RT

2006-2

013

@Immutable class Person { String first, last }

Page 39: Leveraging Groovy for Capturing Business Rules

AST Transforms • @AutoClone

• @AutoExternalize

• @Bindable

• @Canonical

• @Category

• @ConditionalInterrupt

• @Delegate

• @EqualsAndHashCode

• @Field

• @Immutable

• @IndexedProperty

• @InheritConstructors

• @PackageScope

• @Synchronized

• @ThreadInterrupt

• @TimedInterrupt

• @ToString

• @TupleConstructor

• @WithReadLock

• @WithWriteLock

• @Grab

– @GrabConfig

– @GrabResolver

– @GrabExclude

• @Grapes

• @Lazy

• @Mixin

• @Newify

• @Singleton

• @CompileStatic

• @TypeChecked

© A

SE

RT

2006-2

013

Page 40: Leveraging Groovy for Capturing Business Rules

Drools Expert Golfing Example… /* * Copyright 2010 JBoss Inc * Licensed under the Apache License… */ package org.drools.examples.golfing; import org.drools.KnowledgeBase; import org.drools.KnowledgeBaseFactory; import org.drools.builder.KnowledgeBuilder; import org.drools.builder.KnowledgeBuilderFactory; import org.drools.builder.ResourceType; import org.drools.io.ResourceFactory; import org.drools.runtime.StatefulKnowledgeSession; public class GolfingExample { /** * @param args */ public static void main(final String[] args) { final KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder(); kbuilder.add(ResourceFactory.newClassPathResource("golf.drl", GolfingExample.class), ResourceType.DRL); final KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase(); kbase.addKnowledgePackages(kbuilder.getKnowledgePackages()); final StatefulKnowledgeSession ksession = kbase.newStatefulKnowledgeSession(); String[] names = new String[]{"Fred", "Joe", "Bob", "Tom"}; String[] colors = new String[]{"red", "blue", "plaid", "orange"}; int[] positions = new int[]{1, 2, 3, 4}; for (int n = 0; n < names.length; n++) { for (int c = 0; c < colors.length; c++) { for (int p = 0; p < positions.length; p++) { ksession.insert(new Golfer(names[n], colors[c], positions[p])); } } } // ...

// ... ksession.fireAllRules(); ksession.dispose(); } public static class Golfer { private String name; private String color; private int position; public Golfer() { } public Golfer(String name, String color, int position) { super(); this.name = name; this.color = color; this.position = position; } /** * @return the color */ public String getColor() { return this.color; } /** * @return the name */ public String getName() { return this.name; } /** * @return the name */ public int getPosition() { return this.position; } } }

Page 41: Leveraging Groovy for Capturing Business Rules

…Drools Expert Golfing Example import groovy.transform.Immutable import static org.drools.builder.ResourceType.DRL import static org.drools.KnowledgeBaseFactory.newKnowledgeBase import static org.drools.builder.KnowledgeBuilderFactory.newKnowledgeBuilder import static org.drools.io.ResourceFactory.newClassPathResource

def kbuilder = newKnowledgeBuilder() kbuilder.add(newClassPathResource("golf.drl", getClass()), DRL) def kbase = newKnowledgeBase() kbase.addKnowledgePackages(kbuilder.knowledgePackages) def ksession = kbase.newStatefulKnowledgeSession()

def names = ["Fred", "Joe", "Bob", "Tom"] def colors = ["red", "blue", "plaid", "orange"] def positions = [1, 2, 3, 4] [names, colors, positions].combinations().each { n, c, p -> ksession.insert(new Golfer(n, c, p)) } ksession.fireAllRules() ksession.dispose()

@Immutable class Golfer { String name String color int position }

Page 42: Leveraging Groovy for Capturing Business Rules

…Drools Expert Golfing Example import groovy.transform.Immutable import static org.drools.builder.ResourceType.DRL import static org.drools.KnowledgeBaseFactory.newKnowledgeBase import static org.drools.builder.KnowledgeBuilderFactory.newKnowledgeBuilder import static org.drools.io.ResourceFactory.newClassPathResource

def kbuilder = newKnowledgeBuilder() kbuilder.add(newClassPathResource("golf.drl", getClass()), DRL) def kbase = newKnowledgeBase() kbase.addKnowledgePackages(kbuilder.knowledgePackages) def ksession = kbase.newStatefulKnowledgeSession()

def names = ["Fred", "Joe", "Bob", "Tom"] def colors = ["red", "blue", "plaid", "orange"] def positions = [1, 2, 3, 4] [names, colors, positions].combinations().each { n, c, p -> ksession.insert(new Golfer(n, c, p)) } ksession.fireAllRules() ksession.dispose()

@Immutable class Golfer { String name String color int position }

Page 43: Leveraging Groovy for Capturing Business Rules

…Drools Expert Golfing Example import groovy.transform.Immutable import static org.drools.builder.ResourceType.DRL import static org.drools.KnowledgeBaseFactory.newKnowledgeBase import static org.drools.builder.KnowledgeBuilderFactory.newKnowledgeBuilder import static org.drools.io.ResourceFactory.newClassPathResource

def kbuilder = newKnowledgeBuilder() kbuilder.add(newClassPathResource("golf.drl", getClass()), DRL) def kbase = newKnowledgeBase() kbase.addKnowledgePackages(kbuilder.knowledgePackages) def ksession = kbase.newStatefulKnowledgeSession()

def names = ["Fred", "Joe", "Bob", "Tom"] def colors = ["red", "blue", "plaid", "orange"] def positions = [1, 2, 3, 4] [names, colors, positions].combinations().each { n, c, p -> ksession.insert(new Golfer(n, c, p)) } ksession.fireAllRules() ksession.dispose()

@Immutable class Golfer { String name String color int position }

Page 44: Leveraging Groovy for Capturing Business Rules

@Grab…

• Set up dependencies as per Java – E.g. manually add to classpath or use

Maven/Gradle/Ant or rely on IDE features

• Or @Grab declares dependencies inline – Makes scripts environment independent

– Downloads transient dependencies as needed

– (Uncomment in github examples)

//@GrabResolver('https://repository.jboss.org/nexus/content/groups/public-jboss/') //@Grab('org.drools:knowledge-api:5.4.0.Final') //@Grab('org.drools:knowledge-internal-api:5.4.0.Final') //@Grab('com.sun.xml.bind:jaxb-xjc:2.2.5.jboss-1') //@GrabExclude('com.github.relaxng:relaxngDatatype') import groovy.transform.Immutable import org.drools.builder.ResourceType import static org.drools.KnowledgeBaseFactory.newKnowledgeBase import static org.drools.builder.KnowledgeBuilderFactory.newKnowledgeBuilder import static org.drools.io.ResourceFactory.newClassPathResource def kbuilder = newKnowledgeBuilder() // ...

Page 45: Leveraging Groovy for Capturing Business Rules

…@Grab…

> groovy -classpath C:\Projects\GroovyProblemSolvers\out\production\DroolsExpert; C:\Users\paulk\.groovy\grapes\org.drools\drools-compiler\jars\drools-compiler-5.3.3.Final.jar; C:\Users\paulk\.groovy\grapes\org.antlr\antlr-runtime\jars\antlr-runtime-3.3.jar; C:\Users\paulk\.groovy\grapes\org.antlr\antlr\jars\antlr-3.3.jar; C:\Users\paulk\.groovy\grapes\org.antlr\stringtemplate\jars\stringtemplate-3.2.1.jar; C:\Users\paulk\.groovy\grapes\antlr\antlr\jars\antlr-2.7.7.jar; C:\Users\paulk\.groovy\grapes\org.eclipse.jdt.core.compiler\ecj\jars\ecj-3.5.1.jar; C:\Users\paulk\.groovy\grapes\org.mvel\mvel2\jars\mvel2-2.1.0.drools16.jar; C:\Users\paulk\.groovy\grapes\com.sun.xml.bind\jaxb-xjc\jars\jaxb-xjc-2.2.5.jboss-1.jar; C:\Users\paulk\.groovy\grapes\com.sun.xml.bind\jaxb-impl\jars\jaxb-impl-2.2.5.jboss-1.jar; C:\Users\paulk\.groovy\grapes\javax.xml.bind\jaxb-api\jars\jaxb-api-2.2.6.jar; C:\Users\paulk\.groovy\grapes\com.sun.istack\istack-commons-runtime\jars\istack-commons-runtime-2.6.1.jar; C:\Users\paulk\.groovy\grapes\javax.xml.stream\stax-api\jars\stax-api-1.0-2.jar; C:\Users\paulk\.groovy\grapes\javax.activation\activation\jars\activation-1.1.jar; C:\Users\paulk\.groovy\grapes\com.sun.xml.txw2\txw2\jars\txw2-20110809.jar; C:\Users\paulk\.groovy\grapes\relaxngDatatype\relaxngDatatype\jars\relaxngDatatype-20020414.jar; C:\Users\paulk\.groovy\grapes\com.sun.codemodel\codemodel\jars\codemodel-2.6.jar; C:\Users\paulk\.groovy\grapes\com.sun.xml.dtd-parser\dtd-parser\jars\dtd-parser-1.1.jboss-1.jar; C:\Users\paulk\.groovy\grapes\com.sun.istack\istack-commons-tools\jars\istack-commons-tools-2.6.1.jar; C:\Users\paulk\.groovy\grapes\org.apache.ant\ant\jars\ant-1.7.0.jar; C:\Users\paulk\.groovy\grapes\org.apache.ant\ant-launcher\jars\ant-launcher-1.7.0.jar; C:\Users\paulk\.groovy\grapes\org.kohsuke.rngom\rngom\jars\rngom-201103.jboss-1.jar; C:\Users\paulk\.groovy\grapes\com.sun.xsom\xsom\jars\xsom-20110809.jar; C:\Users\paulk\.groovy\grapes\xml-resolver\xml-resolver\jars\xml-resolver-1.1.jar; C:\Users\paulk\.groovy\grapes\org.drools\drools-core\jars\drools-core-5.3.3.Final.jar; C:\Users\paulk\.groovy\grapes\org.drools\knowledge-api\jars\knowledge-api-5.3.3.Final.jar; C:\Projects\GroovyProblemSolvers\DroolsExpert\src\resources GolfExample.groovy

Or you can precompile: > groovyc -classpath ... GolfExample.groovy > jar ... Then use groovy or java commands to run.

Old school

Page 46: Leveraging Groovy for Capturing Business Rules

…@Grab…

> groovy GolfExample.groovy

@GrabResolver('https://repository.jboss.org/nexus/content/groups/public-jboss/') @Grab('org.drools:knowledge-api:5.4.0.Final') @Grab('org.drools:knowledge-internal-api:5.4.0.Final') @Grab('com.sun.xml.bind:jaxb-xjc:2.2.5.jboss-1') @GrabExclude('com.github.relaxng:relaxngDatatype') import groovy.transform.Immutable import org.drools.builder.ResourceType import static org.drools.KnowledgeBaseFactory.newKnowledgeBase import static org.drools.builder.KnowledgeBuilderFactory.newKnowledgeBuilder import static org.drools.io.ResourceFactory.newClassPathResource def kbuilder = newKnowledgeBuilder() // ...

With @Grab

Page 47: Leveraging Groovy for Capturing Business Rules

…@Grab

@GrabResolver('https://repository.jboss.org/nexus/content/groups/public-jboss/') @Grab('org.drools:knowledge-api:5.4.0.Final') @Grab('org.drools:knowledge-internal-api:5.4.0.Final') @Grab('com.sun.xml.bind:jaxb-xjc:2.2.5.jboss-1') @GrabExclude('com.github.relaxng:relaxngDatatype') import groovy.transform.Immutable import org.drools.builder.ResourceType import static org.drools.KnowledgeBaseFactory.newKnowledgeBase import static org.drools.builder.KnowledgeBuilderFactory.newKnowledgeBuilder import static org.drools.io.ResourceFactory.newClassPathResource def kbuilder = newKnowledgeBuilder() // ...

> groovy GolfExample

With @Grab

Page 48: Leveraging Groovy for Capturing Business Rules

Topics

• Introduction to DSLs

• Introduction to Groovy

• DSLs in Groovy

• Why Groovy?

Tortoise & Crane Example

• Einstein’s Riddle

• Further Discussion

• More Info

© A

SE

RT

2006-2

013

Page 49: Leveraging Groovy for Capturing Business Rules

Tortoises & Cranes

• Around a pond dwell tortoises and cranes

• There are 7 animals in total

• There are 20 legs in total

• How many of each animal are there?

Source: http://www.youtube.com/watch?v=tUs4olWQYS4

Page 50: Leveraging Groovy for Capturing Business Rules

Tortoises & Cranes: Choco… //@GrabResolver('http://www.emn.fr/z-info/choco-repo/mvn/repository') //@Grab('choco:choco-solver:2.1.5') import static choco.Choco.* import choco.cp.model.CPModel import choco.cp.solver.CPSolver

def m = new CPModel() def s = new CPSolver()

def totalAnimals = 7 def totalLegs = 20 def c = makeIntVar('Cranes', 0, totalAnimals) def t = makeIntVar('Tortoises', 0, totalAnimals) m.addConstraint(eq(plus(c, t), totalAnimals)) m.addConstraint(eq(plus(mult(c, 2), mult(t, 4)), totalLegs)) s.read(m)

def more = s.solve() while (more) { println "Found a solution:" [c, t].each { def v = s.getVar(it) if (v.val) println " $v.val * $v.name" } more = s.nextSolution() }

Found a solution:

4 * Cranes

3 * Tortoises

Page 51: Leveraging Groovy for Capturing Business Rules

…Tortoises & Cranes: Choco import static choco.Choco.* import choco.cp.model.CPModel import choco.cp.solver.CPSolver import choco.kernel.model.variables.integer.IntegerVariable

def m = new CPModel() def s = new CPSolver()

def totalAnimals = 7 def totalLegs = 20 def c = makeIntVar('Cranes', 0, totalAnimals) def t = makeIntVar('Tortoises', 0, totalAnimals) IntegerVariable[] animals = [c, t] m.addConstraint(eq(plus(c, t), totalAnimals)) m.addConstraint(eq(scalar(animals, [2, 4] as int[]), totalLegs)) s.read(m)

def more = s.solve() while (more) { println "Found a solution:" animals.each { def v = s.getVar(it) if (v.val) println " $v.val * $v.name" } more = s.nextSolution() }

Slight variant

using scalars.

Well suited to

scaling to

more animals

Page 52: Leveraging Groovy for Capturing Business Rules

Tortoises & Cranes: Simplistic… import groovy.transform.Immutable import org.drools.builder.ResourceType import static org.drools.KnowledgeBaseFactory.newKnowledgeBase import static org.drools.builder.KnowledgeBuilderFactory.newKnowledgeBuilder import static org.drools.io.ResourceFactory.newReaderResource

def numAnimals = 7 def numLegs = 20 def kbuilder = newKnowledgeBuilder() kbuilder.add(newReaderResource(new StringReader(''' dialect "mvel" rule "deduce animal counts" when $crane : Crane( ) $tortoise : Tortoise( quantity + $crane.quantity == ''' + numAnimals + ''', quantity * numLegs + $crane.quantity * $crane.numLegs == ''' + numLegs + ''' ) then System.out.println( "Cranes " + $crane.getQuantity() ) System.out.println( "Tortoises " + $tortoise.getQuantity() ) end ''')), ResourceType.DRL) def kbase = newKnowledgeBase() kbase.addKnowledgePackages(kbuilder.knowledgePackages) def ksession = kbase.newStatefulKnowledgeSession() …

Page 53: Leveraging Groovy for Capturing Business Rules

import groovy.transform.Immutable import org.drools.builder.ResourceType import static org.drools.KnowledgeBaseFactory.newKnowledgeBase import static org.drools.builder.KnowledgeBuilderFactory.newKnowledgeBuilder import static org.drools.io.ResourceFactory.newReaderResource

def numAnimals = 7 def numLegs = 20 def kbuilder = newKnowledgeBuilder() kbuilder.add(newReaderResource(new StringReader(''' dialect "mvel" rule "deduce animal counts" when $crane : Crane( ) $tortoise : Tortoise( quantity + $crane.quantity == ''' + numAnimals + ''', quantity * numLegs + $crane.quantity * $crane.numLegs == ''' + numLegs + ''' ) then System.out.println( "Cranes " + $crane.getQuantity() ) System.out.println( "Tortoises " + $tortoise.getQuantity() ) end ''')), ResourceType.DRL) def kbase = newKnowledgeBase() kbase.addKnowledgePackages(kbuilder.knowledgePackages) def ksession = kbase.newStatefulKnowledgeSession() …

…Tortoises & Cranes: Simplistic… … (numAnimals + 1).times { n -> if (numLegs.intdiv(Crane.numLegs) >= n) { ksession.insert(new Crane(n)) } if (numLegs.intdiv(Tortoise.numLegs) >= n) { ksession.insert(new Tortoise(n)) } } ksession.fireAllRules() ksession.dispose() @Immutable class Crane { static int numLegs = 2 int quantity } @Immutable class Tortoise { static int numLegs = 4 int quantity }

Page 54: Leveraging Groovy for Capturing Business Rules

import groovy.transform.Immutable import org.drools.builder.ResourceType import static org.drools.KnowledgeBaseFactory.newKnowledgeBase import static org.drools.builder.KnowledgeBuilderFactory.newKnowledgeBuilder import static org.drools.io.ResourceFactory.newReaderResource

def numAnimals = 7 def numLegs = 20 def kbuilder = newKnowledgeBuilder() kbuilder.add(newReaderResource(new StringReader(''' dialect "mvel" rule "deduce animal counts" when $crane : Crane( ) $tortoise : Tortoise( quantity + $crane.quantity == ''' + numAnimals + ''', quantity * numLegs + $crane.quantity * $crane.numLegs == ''' + numLegs + ''' ) then System.out.println( "Cranes " + $crane.getQuantity() ) System.out.println( "Tortoises " + $tortoise.getQuantity() ) end ''')), ResourceType.DRL) def kbase = newKnowledgeBase() kbase.addKnowledgePackages(kbuilder.knowledgePackages) def ksession = kbase.newStatefulKnowledgeSession() …

…Tortoises & Cranes: Simplistic … (numAnimals + 1).times { n -> if (numLegs.intdiv(Crane.numLegs) >= n) { ksession.insert(new Crane(n)) } if (numLegs.intdiv(Tortoise.numLegs) >= n) { ksession.insert(new Tortoise(n)) } } ksession.fireAllRules() ksession.dispose() @Immutable class Crane { static int numLegs = 2 int quantity } @Immutable class Tortoise { static int numLegs = 4 int quantity }

What is the impact

of adding another

kind of animal?

Page 55: Leveraging Groovy for Capturing Business Rules

Tortoises & Cranes: DSL… //@GrabResolver('https://repository.jboss.org/nexus/content/groups/public-jboss/') //@Grab('org.drools:knowledge-api:5.4.0.Final') //@Grab('org.drools:drools-compiler:5.4.0.Final') //@Grab('org.drools:drools-core:5.4.0.Final') //@Grab('com.sun.xml.bind:jaxb-xjc:2.2.5.jboss-1') //@GrabExclude('com.github.relaxng:relaxngDatatype')

import groovy.transform.Field import org.drools.builder.ResourceType import static org.drools.KnowledgeBaseFactory.* import static org.drools.builder.KnowledgeBuilderFactory.* import static org.drools.io.ResourceFactory.newReaderResource

class Solver { static main(Map animals, int totalAnimals, int totalLegs, ClassLoader loader) { def whenClauses = '' def thenClauses = '' def numAnimalsClause = '' def numLegsClause = '' def lastIndex = animals.size() - 1 animals.eachWithIndex { entry, index -> def key = entry.key def capKey = key.capitalize() whenClauses += ' $' + "$key : $capKey (" thenClauses += " System.out.println( \"$capKey \"" + ' + $' + key + '.getQuantity() )\n' if (index != lastIndex) { numAnimalsClause += ' + $' + key + '.quantity' numLegsClause += ' + $' + key + '.quantity * $' + key + '.numLegs' whenClauses += ' )\n' } else { whenClauses += '\n quantity' + numAnimalsClause + ' == ' + totalAnimals + ',' whenClauses += '\n quantity * numLegs' + numLegsClause + ' == ' + totalLegs whenClauses += '\n )\n' } } …

Page 56: Leveraging Groovy for Capturing Business Rules

…Tortoises & Cranes: DSL… … def drl = ''' dialect "mvel" rule "deduce animal counts" when ''' + whenClauses + ''' then ''' + thenClauses + '''end ''' def kbuilderConf = newKnowledgeBuilderConfiguration(null, loader) def kbuilder = newKnowledgeBuilder(kbuilderConf) kbuilder.add(newReaderResource(new StringReader(drl)), ResourceType.DRL) def kbaseConf = newKnowledgeBaseConfiguration(null, loader) def kbase = newKnowledgeBase(kbaseConf) kbase.addKnowledgePackages(kbuilder.knowledgePackages) def ksession = kbase.newStatefulKnowledgeSession() (totalAnimals + 1).times { n -> animals.each { key, val -> def capKey = key.capitalize() Class animal = loader.loadClass(capKey) if (totalLegs.intdiv(animal.numLegs) >= n) { ksession.insert(animal.newInstance(n)) } } } ksession.fireAllRules() ksession.dispose() } } …

Page 57: Leveraging Groovy for Capturing Business Rules

…Tortoises & Cranes: DSL… … @Field animalProps = [:] def props = [:] def methodMissing(String name, _have) { new AnimalHolder(animals: animalProps, name: name) } def propertyMissing(String name) { name } class ThereHolder { def props def methodMissing(String name, args) { props['total' + args[0].capitalize()] = name.toInteger() } } class AnimalHolder { def animals, name def methodMissing(String number, args) { animals[name] = number.toInteger() } } def there = { _are -> new ThereHolder(props: props) }

Page 58: Leveraging Groovy for Capturing Business Rules

…Tortoises & Cranes: DSL … cranes have 2 legs tortoises have 4 legs //millipedes have 1000 legs there are 7 animals //there are 8 animals there are 20 legs //there are 1020 legs new GroovyShell([animals: animalProps] as Binding).evaluate( animalProps.collect { key, val -> def capKey = key.capitalize() """ @groovy.transform.Immutable class $capKey { static int numLegs = $val int quantity } """ }.join('\n') + "Solver.main(animals, $props.totalAnimals, $props.totalLegs, getClass().classLoader)" )

Cranes 4

Tortoises 3

Cranes 4

Tortoises 3

Millipedes 1

Page 59: Leveraging Groovy for Capturing Business Rules

Topics

• Introduction to DSLs

• Introduction to Groovy

• DSLs in Groovy

• Why Groovy?

• Tortoise & Crane Example

Einstein’s Riddle

• Further Discussion

• More Info

© A

SE

RT

2006-2

013

Page 60: Leveraging Groovy for Capturing Business Rules

Einstein’s Riddle…

• Wikipedia: The zebra puzzle is a well-

known logic puzzle – It is often called Einstein's Puzzle or Einstein's Riddle

because it is said to have been invented by Albert

Einstein as a boy, with the claim that Einstein said

“… only 2 percent of the world's population can solve it.”

– The puzzle is also sometimes attributed to Lewis Carroll.

However, there is no known evidence for Einstein's or

Carroll's authorship; and the original puzzle cited

mentions brands of cigarette, such as Kools, that did not

exist during Carroll's lifetime or Einstein's boyhood

© A

SE

RT

2006-2

013

Page 61: Leveraging Groovy for Capturing Business Rules

…Einstein’s Riddle

© A

SE

RT

2006-2

013

• Some premises: – The British person lives in the red house

– The Swede keeps dogs as pets

– The Dane drinks tea

– The green house is on the left of the white house

– The green homeowner drinks coffee

– The man who smokes Pall Mall keeps birds

– The owner of the yellow house smokes Dunhill

– The man living in the center house drinks milk

– The Norwegian lives in the first house

– The man who smokes Blend lives next to the one who keeps cats

– The man who keeps the horse lives next to the man who smokes Dunhill

– The man who smokes Bluemaster drinks beer

– The German smokes Prince

– The Norwegian lives next to the blue house

– The man who smokes Blend has a neighbor who drinks water

• And a question: – Who owns the fish?

Page 62: Leveraging Groovy for Capturing Business Rules

Einstein’s Riddle : Prolog

© A

SE

RT

2006-2

013

% from http://www.baptiste-wicht.com/2010/09/solve-einsteins-riddle-using-prolog % Preliminary definitions persons(0, []) :- !. persons(N, [(_Men,_Color,_Drink,_Smoke,_Animal)|T]) :- N1 is N-1, persons(N1,T). person(1, [H|_], H) :- !. person(N, [_|T], R) :- N1 is N-1, person(N1, T, R). % The Brit lives in a red house hint1([(brit,red,_, _, _)|_]). hint1([_|T]) :- hint1(T). % The Swede keeps dogs as pets hint2([(swede,_,_,_,dog)|_]). hint2([_|T]) :- hint2(T). % The Dane drinks tea hint3([(dane,_,tea,_,_)|_]). hint3([_|T]) :- hint3(T). % The Green house is on the left of the White house hint4([(_,green,_,_,_),(_,white,_,_,_)|_]). hint4([_|T]) :- hint4(T). % The owner of the Green house drinks coffee. hint5([(_,green,coffee,_,_)|_]). hint5([_|T]) :- hint5(T). ...

Page 63: Leveraging Groovy for Capturing Business Rules

Einstein’s Riddle : Polyglot

© A

SE

RT

2006-2

013

@GrabResolver('http://dev.inf.unideb.hu:8090/archiva/repository/internal') //@Grab('jlog:jlogic-debug:1.3.6') @Grab('org.prolog4j:prolog4j-api:0.2.0') // uncomment one of the next three lines //@Grab('org.prolog4j:prolog4j-jlog:0.2.0') @Grab('org.prolog4j:prolog4j-tuprolog:0.2.0') //@Grab('org.prolog4j:prolog4j-jtrolog:0.2.0') import org.prolog4j.* def p = ProverFactory.prover p.addTheory(new File('/GroovyExamples/tuProlog/src/einstein.pl').text) def sol = p.solve("solution(Persons).") //println sol.solution.get('Persons') // jlog to avoid converter println sol.get('Persons') // jtrolog/tuProlog

Page 64: Leveraging Groovy for Capturing Business Rules

Einstein’s Riddle : Polyglot w/ DSL…

© A

SE

RT

2006-2

013

// define some domain classes and objects enum Pet { dog, cat, bird, fish, horse } enum Color { green, white, red, blue, yellow } enum Smoke { dunhill, blends, pallmall, prince, bluemaster } enum Drink { water, tea, milk, coffee, beer } enum Nationality { Norwegian, Dane, Brit, German, Swede } dogs = dog; birds = bird; cats = cat; horses = horse a = owner = house = the = abode = person = man = is = to = side = next = who = different = 'ignored'

// some preliminary definitions p = ProverFactory.prover hintNum = 1 p.addTheory(''' persons(0, []) :- !. persons(N, [(_Men,_Color,_Drink,_Smoke,_Animal)|T]) :- N1 is N-1, persons(N1,T). person(1, [H|_], H) :- !. person(N, [_|T], R) :- N1 is N-1, person(N1, T, R). ''')

Page 65: Leveraging Groovy for Capturing Business Rules

…Einstein’s Riddle : Polyglot w/ DSL…

© A

SE

RT

2006-2

013

// define some helper methods (our interface to prolog) def addPairHint(Map m) { def from = m.from?.toString()?.toLowerCase() p.addTheory(""" hint$hintNum([(${from ?: '_'},${m.color ?: '_'},${m.drink ?: '_'},${m.smoke ?: '_'},${m.pet ?: '_'})|_]). hint$hintNum([_|T]) :- hint$hintNum(T). """) hintNum++ }

def addPositionHint(Map m, int pos) { def from = m.from?.toString()?.toLowerCase() p.addTheory(""" hint$hintNum(Persons) :- person($pos, Persons, (${from ?: '_'},${m.color ?: '_'},${m.drink ?: '_'},${m.smoke ?: '_'},${m.pet ?: '_'})). """) hintNum++ }

def addToLeftHint(Map left, Map right) { p.addTheory(""" hint$hintNum([(_,$left.color,_,_,_),(_,$right.color,_,_,_)|_]). hint$hintNum([_|T]) :- hint$hintNum(T). """) hintNum++ } ...

Page 66: Leveraging Groovy for Capturing Business Rules

…Einstein’s Riddle : Polyglot w/ DSL…

© A

SE

RT

2006-2

013

// now implement DSL in terms of helper methods def the(Nationality n) { def ctx = [from:n] [ drinks: { d -> addPairHint(ctx + [drink:d]) }, smokes: { s -> addPairHint(ctx + [smoke:s]) }, keeps: { p -> addPairHint(ctx + [pet:p]) }, rears: { p -> addPairHint(ctx + [pet:p]) }, owns:{ _the -> [first:{ house -> addPositionHint(ctx, 1) }] }, has:{ _a -> [pet: { a -> addPairHint(ctx + [pet:a]) }] + Color.values().collectEntries{ c -> [c.toString(), { _dummy -> addPairHint(ctx + [color:c]) } ] } }, lives: { _next -> [to: { _the -> Color.values().collectEntries{ c -> [c.toString(), { _dummy -> addNeighbourHint(ctx, [color:c]) } ] } }]} ] } ...

Page 67: Leveraging Groovy for Capturing Business Rules

…Einstein’s Riddle : Polyglot w/ DSL…

© A

SE

RT

2006-2

013

// now define the DSL the man from the centre house drinks milk the Norwegian owns the first house the Dane drinks tea the German smokes prince the Swede keeps dogs // alternate ending: has a pet dog the Brit has a red house // alternate ending: red abode the owner of the green house drinks coffee the owner of the yellow house smokes dunhill the person known to smoke pallmall rears birds // other ending: keeps birds the man known to smoke bluemaster drinks beer the green house is on the left side of the white house the man known to smoke blends lives next to the one who keeps cats the man known to keep horses lives next to the man who smokes dunhill the man known to smoke blends lives next to the one who drinks water the Norwegian lives next to the blue house

Page 68: Leveraging Groovy for Capturing Business Rules

…Einstein’s Riddle : Polyglot w/ DSL…

© A

SE

RT

2006-2

013

// now implement DSL in terms of helper methods def the(Nationality n) { def ctx = [from:n] [ drinks: { d -> addPairHint(ctx + [drink:d]) }, smokes: { s -> addPairHint(ctx + [smoke:s]) }, keeps: { p -> addPairHint(ctx + [pet:p]) }, ... ] } ...

the German smokes prince

the(German).smokes(prince)

n = German ctx = [from: German] [drinks: …, smokes: { s -> addPairHint([from: German, smoke: s]) }, keeps: …, … ] addPairHint([from: German, smoke: prince])

Page 69: Leveraging Groovy for Capturing Business Rules

…Einstein’s Riddle : Polyglot w/ DSL…

• Some parts of our DSL are automatically

statically inferred, e.g. typing ‘bl’ and then

asking for completion yields:

• But other parts are not known, e.g. the

word ‘house’ in the fragment below:

© A

SE

RT

2006-2

013

‘house’ is key for a Map and could be any value

Page 70: Leveraging Groovy for Capturing Business Rules

…Einstein’s Riddle : Polyglot w/ DSL

© A

SE

RT

2006-2

013

class HousePlaceHolder { def c1, script def house(_is) { [on: { _the -> [left: { _side -> [of: { __the -> Color.values().collectEntries { c2 -> [c2.toString(), { _dummy -> script.addToLeftHint( [color: c1], [color: c2] )}]} }]}]}] } } def the(Color c1) { new HousePlaceHolder(c1:c1, script:this) }

def the(Color c1) {[ house: { _is -> [on: { _the -> [left: { _side -> [of: { __the -> Color.values().collectEntries{ c2 -> [c2.toString(), { _dummy -> addToLeftHint([color:c1], [color:c2]) }]} }]}]}]} ]}

‘house’ is now understood

We can choose to introduce

additional static typing

information into our DSL

implementation or ‘teach’

our IDE about or DSL.

Page 71: Leveraging Groovy for Capturing Business Rules

Einstein’s Riddle : Choco DSL…

© A

SE

RT

2006-2

013

//@GrabResolver('http://www.emn.fr/z-info/choco-repo/mvn/repository') //@Grab('choco:choco-solver:2.1.5') import static choco.Choco.* import choco.kernel.model.variables.integer.* import groovy.transform.Field enum Pet { dog, cat, bird, fish, horse } enum Color { green, white, red, blue, yellow } enum Sport { baseball, volleyball, football, hockey, tennis } enum Drink { water, tea, milk, coffee, beer } enum Nationality { Norwegian, Dane, Briton, German, Swede } import static Pet.* import static Color.* import static Sport.* import static Drink.* import static Nationality.* // define logic solver data structures num = 5 center = 2 first = 0 println "Solving Einstein's Riddle:" …

Page 72: Leveraging Groovy for Capturing Business Rules

…Einstein’s Riddle : Choco DSL…

© A

SE

RT

2006-2

013

… @Field m = new choco.cp.model.CPModel() def s = new choco.cp.solver.CPSolver() choco.Choco.metaClass.static.eq = { c, v -> delegate.eq(c, v.ordinal()) } def makeEnumVar(st, arr) { choco.Choco.makeIntVar(st, 0, arr.size()-1, choco.Options.V_ENUM) } pets = new IntegerVariable[num] colors = new IntegerVariable[num] plays = new IntegerVariable[num] drinks = new IntegerVariable[num] nations = new IntegerVariable[num] (0..<num).each { i -> pets[i] = makeEnumVar("pet$i", pets) colors[i] = makeEnumVar("color$i", colors) plays[i] = makeEnumVar("plays$i", plays) drinks[i] = makeEnumVar("drink$i", drinks) nations[i] = makeEnumVar("nation$i", nations) } def pretty(s, c, arr, i) { c.values().find{ it.ordinal() == s.getVar(arr[i])?.value } } …

Page 73: Leveraging Groovy for Capturing Business Rules

…Einstein’s Riddle : Choco DSL…

© A

SE

RT

2006-2

013

… // define DSL (simplistic non-refactored version) def neighbours(var1, val1, var2, val2) { m.addConstraint and( ifOnlyIf(eq(var1[0], val1), eq(var2[1], val2)), implies(eq(var1[1], val1), or(eq(var2[0], val2), eq(var2[2], val2))), implies(eq(var1[2], val1), or(eq(var2[1], val2), eq(var2[3], val2))), implies(eq(var1[3], val1), or(eq(var2[2], val2), eq(var2[4], val2))), ifOnlyIf(eq(var1[4], val1), eq(var2[3], val2)) ) } iff = { e1, c1, e2, c2 -> m.addConstraint and(*(0..<num).collect{ ifOnlyIf(eq(e1[it], c1), eq(e2[it], c2)) }) } isEq = { a, b -> m.addConstraint eq(a, b) } dogs = dog; birds = bird; cats = cat; horses = horse a = owner = house = the = abode = person = man = to = is = side = next = who = different = 'ignored' …

Page 74: Leveraging Groovy for Capturing Business Rules

…Einstein’s Riddle : Choco DSL…

© A

SE

RT

2006-2

013

… // define the DSL in terms of DSL implementation def the(Nationality n) { def ctx = [nations, n] [ drinks:iff.curry(*ctx, drinks), plays:iff.curry(*ctx, plays), keeps:iff.curry(*ctx, pets), rears:iff.curry(*ctx, pets), owns:{ _the -> [first:{ house -> isEq(nations[first], n)}] }, has:{ _a -> [pet:iff.curry(*ctx, pets)] + Color.values().collectEntries{ c -> [c.toString(), { _dummy -> iff(*ctx, colors, c) } ] } }, lives: { _next -> [to: { _the -> Color.values().collectEntries{ c -> [c.toString(), { _dummy -> neighbours(*ctx, colors, c) } ] } }]} ] } …

Page 75: Leveraging Groovy for Capturing Business Rules

…Einstein’s Riddle : Choco DSL…

© A

SE

RT

2006-2

013

… def the(Color c1) {[ house: { _is -> [on: { _the -> [left: { _side -> [of: { __the -> Color.values().collectEntries{ c2 -> [c2.toString(), { _dummy -> m.addConstraint and(*(1..<num).collect{ ifOnlyIf(eq(colors[it-1], c1), eq(colors[it], c2)) }) }]} }]}]}]} ]} def the(String _dummy) {[ of:{ _the -> Color.values().collectEntries{ c -> [c.toString(), { _house -> [ drinks:iff.curry(colors, c, drinks), plays:iff.curry(colors, c, plays) ] } ] } }, known: { _to -> [ play: { sport -> def ctx = [plays, sport] [ rears: iff.curry(*ctx, pets), keeps: iff.curry(*ctx, pets), drinks: iff.curry(*ctx, drinks), lives: { _next -> [to: { _the -> [one: { _who -> [ keeps: { pet -> neighbours(pets, pet, *ctx) }, drinks: { beverage -> neighbours(drinks, beverage, *ctx) } ]}]}]} ] }, …

Page 76: Leveraging Groovy for Capturing Business Rules

…Einstein’s Riddle : Choco DSL…

© A

SE

RT

2006-2

013

… keep : { pet -> [ lives: { _next -> [to: { _the -> [man: { _who -> [ plays: { sport -> neighbours(pets, pet, plays, sport) } ]}]}]} ]} ]}, from: { _the -> [center: { house -> [drinks: { d -> isEq(drinks[center], d)}] }]} ]} def all(IntegerVariable[] var) { [are: { _different -> m.addConstraint allDifferent(var) } ] } …

Page 77: Leveraging Groovy for Capturing Business Rules

…Einstein’s Riddle : Choco DSL

© A

SE

RT

2006-2

013

… // define rules all pets are different all colors are different all plays are different all drinks are different all nations are different the man from the center house drinks milk the Norwegian owns the first house the Dane drinks tea the German plays hockey the Swede keeps dogs // alternate ending: has a pet dog the Briton has a red house // alternate ending: red abode the owner of the green house drinks coffee the owner of the yellow house plays baseball the person known to play football rears birds // alternate ending: keeps birds the man known to play tennis drinks beer the green house is on the left side of the white house the man known to play volleyball lives next to the one who keeps cats the man known to keep horses lives next to the man who plays baseball the man known to play volleyball lives next to the one who drinks water the Norwegian lives next to the blue house …

Page 78: Leveraging Groovy for Capturing Business Rules

…Einstein’s Riddle : Choco…

© A

SE

RT

2006-2

013

… // invoke logic solver s.read(m) def more = s.solve() while (more) { for (i in 0..<num) { print 'The ' + pretty(s, Nationality, nations, i) print ' has a pet ' + pretty(s, Pet, pets, i) print ' plays ' + pretty(s, Sport, plays, i) print ' drinks ' + pretty(s, Drink, drinks, i) println ' and lives in a ' + pretty(s, Color, colors, i) + ' house' } more = s.nextSolution() }

Page 79: Leveraging Groovy for Capturing Business Rules

Einstein’s Riddle : Output

© A

SE

RT

2006-2

013

Solving Einstein's Riddle: The Norwegian has a pet cat plays baseball drinks water and lives in a yellow house The Dane has a pet horse plays volleyball drinks tea and lives in a blue house The Briton has a pet bird plays football drinks milk and lives in a red house The German has a pet fish plays hockey drinks coffee and lives in a green house The Swede has a pet dog plays tennis drinks beer and lives in a white house

Page 80: Leveraging Groovy for Capturing Business Rules

Topics

• Introduction to DSLs

• Introduction to Groovy

• DSLs in Groovy

• Why Groovy?

• Tortoise & Crane Example

• Einstein’s Riddle

Further Discussion

• More Info

© A

SE

RT

2006-2

013

Page 81: Leveraging Groovy for Capturing Business Rules

Discussion points

• Choosing granularity

• Choosing the level of dynamic/static typing

• Multi-paradigm solutions

• Capturing Rule Design Patterns using AST

transforms

Page 82: Leveraging Groovy for Capturing Business Rules

Granularity

Solve manners2009

Neighbours must share a hobby Neighbours are of a different gender There should be 2 doctors at each table Each doctor at a table should be a different kind ...

The Guest at position 2 on table 1 should have a different gender to the Guest at position 1 The Guest at position 2 on table 1 should have a different gender to the Guest at position 3 ...

Page 83: Leveraging Groovy for Capturing Business Rules

Typing…

• Dynamic

• Traditional Static Typing

• Stronger levels of Static Typing

Page 84: Leveraging Groovy for Capturing Business Rules

…Typing…

import groovy.transform.TypeChecked import experimental.SprintfTypeCheckingVisitor @TypeChecked(visitor=SprintfTypeCheckingVisitor) 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 85: Leveraging Groovy for Capturing Business Rules

…Typing…

import groovy.transform.TypeChecked import tictactoe.* Import static tictactoe.Position.* @TypeChecked(visitor=TicTacToeTypeVisitor) void main() { Board.empty().move(NW).move(C).move(W).move(SW).move(SE) }

package tictactoe enum Position { NW, N, NE, W, C, E, SW, S, SE } class Board { static Board empty() { new Board() } Board move(Position p) { this } }

Page 86: Leveraging Groovy for Capturing Business Rules

…Typing

import groovy.transform.TypeChecked import tictactoe.* Import static tictactoe.Position.* @TypeChecked(visitor=TicTacToeTypeVisitor) void main() { Board.empty().move(NW).move(C).move(W).move(SW).move(SE) }

package tictactoe enum Position { NW, N, NE, W, C, E, SW, S, SE }

[Static type checking] - Attempt to call suboptimal move SE not allowed [HINT: try NE]

Custom type checker which fails

compilation if programmer attempts

to code a suboptimal solution. Where

suboptimal means doesn’t agree with

what is returned by a minimax,

alpha-beta pruning, iterative

deepening solving engine.

Page 87: Leveraging Groovy for Capturing Business Rules

Multi-paradigm solutions

• Imperative

• Functional – Leveraging immutable data structures

– Persistent data structures

– Higher-order functions

• Rules-based

• Concurrency, e.g. Gpars – Data Parallelism: Map, Reduce

– DataFlow

– Others: Fork Join, Actors

Page 88: Leveraging Groovy for Capturing Business Rules

Using compile-time Metaprogramming

• Powerful mechanism – As illustrated by GOF examples

– @Immutable, @Delegate and others

• Rich area for further research – Explore whether rules design patterns can be readily

embodied within AST transforms

Page 89: Leveraging Groovy for Capturing Business Rules

Topics

• Introduction to DSLs

• Introduction to Groovy

• DSLs in Groovy

• Why Groovy?

• Tortoise & Crane Example

• Einstein’s Riddle

• Further Discussion

More Info

© A

SE

RT

2006-2

013

Page 90: Leveraging Groovy for Capturing Business Rules

More Information: URLs

• Groovy – http://groovy.codehaus.org/

• Groovy DSL talk in general – http://www.slideshare.net/glaforge/groovy-domain-specific-

languages-springone2gx-2013

• Groovy & Other Paradigms – http://www.slideshare.net/paulk_asert/concurrency-with-gpars

– http://www.slideshare.net/paulk_asert/functional-groovy

• Drools Expert & Planner – http://www.jboss.org/drools/

• Choco – http://www.emn.fr/z-info/choco-solver/

Page 91: Leveraging Groovy for Capturing Business Rules

More Information: Groovy in Action 2ed

Contains a

chapter on

DSLs!