Leveraging Groovy for Capturing Business Rules

  • 284 views
Uploaded on

Speaker: Paul King …

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.

  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
No Downloads

Views

Total Views
284
On Slideshare
0
From Embeds
0
Number of Embeds
0

Actions

Shares
Downloads
31
Comments
0
Likes
1

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. © ASERT 2006-2013 Leveraging Groovy for Capturing Business Rules Dr Paul King @paulk_asert http:/slideshare.net/paulk_asert/groovy-rules https://github.com/paulk-asert/groovy-rules
  • 2. Topics © ASERT 2006-2013 Introduction to DSLs • Introduction to Groovy • DSLs in Groovy • Why Groovy? • Tortoise & Crane Example • Einstein’s Riddle • Further Discussion • More Info
  • 3. What is a DSL? © ASERT 2006-2013 • 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
  • 4. 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
  • 5. 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 Compile time translation package org.drools.examples.golfing; dialect "mvel" import org.drools.examples.golfing.GolfingExample.Golfer; rule "find solution" when Bob is wearing plaid pants Compile time or runtime translation // ... then Display all details end when Bob is wearing plaid pants display all details
  • 6. Why a DSL? • Advantages: © ASERT 2006-2013 – 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 generalpurpose programming language constructs – Efficiency costs – Proliferation of similar non-standard DSLs – Tooling
  • 7. Topics © ASERT 2006-2013 • Introduction to DSLs Introduction to Groovy • DSLs in Groovy • Why Groovy? • Tortoise & Crane Example • Einstein’s Riddle • Further Discussion • More Info
  • 8. Java code for list manipulation import java.util.List; import java.util.ArrayList; © ASERT 2006-2013 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
  • 9. Groovy code for list manipulation import java.util.List; import java.util.ArrayList; © ASERT 2006-2013 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
  • 10. Some Java Boilerplate identified import java.util.List; import java.util.ArrayList; © ASERT 2006-2013 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?
  • 11. Java Boilerplate removed © ASERT 2006-2013 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) }
  • 12. More Java Boilerplate identified © ASERT 2006-2013 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?
  • 13. Boilerplate removed = nicer Groovy version names = ["Ted", "Fred", "Jed", "Ned"] println names shortNames = names.findAll{ it.size() < 4 } println shortNames.size() shortNames.each{ println it } © ASERT 2006-2013 Output: ["Ted", "Fred", "Jed", "Ned"] 3 Ted Jed Ned
  • 14. Or Groovy DSL version if required 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 © ASERT 2006-2013 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 }
  • 15. Closures int twice(int arg) { arg * 2 } © ASERT 2006-2013 def triple = { int arg -> arg * 3 } println twice(3) // => 6 println triple(3) // => 9
  • 16. Grapes / Grab: Google collections @Grab('com.google.guava:guava:r09') import com.google.common.collect.HashBiMap HashBiMap fruit = [grape:'purple', lemon:'yellow', lime:'green'] © ASERT 2006-2013 assert fruit.lemon == 'yellow' assert fruit.inverse().yellow == 'lemon'
  • 17. Groovy Builders • Markup Builder © ASERT 2006-2011 import groovy.xml.* def page = new MarkupBuilder() page.html { head { title 'Hello' } body { ul { for (count in 1..5) { li "world $count" } } } } <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>
  • 18. Topics © ASERT 2006-2013 • Introduction to DSLs • Introduction to Groovy DSLs in Groovy • Why Groovy? • Tortoise & Crane Example • Einstein’s Riddle • Further Discussion • More Info
  • 19. DSL example... © ASERT 2006-2013 class def def def } FluentApi { action, what the(what) { this.what = what; this } 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
  • 20. …DSL example... © ASERT 2006-2013 class def def def } DSL implementation details FluentApi { action, what the(what) { this.what = what; this } 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
  • 21. …DSL example... © ASERT 2006-2013 class def def def } FluentApi { action, what the(what) { this.what = what; this } of(arg) { action(what(arg)) } show = { arg -> println arg } square_root = { Math.sqrt(it) } please = { new FluentApi(action: it) } DSL usage please show the square_root of 100 // => 10.0
  • 22. …DSL example... please show the square_root of 100 © ASERT 2006-2013
  • 23. …DSL example... please show the square_root of 100 please(show).the(square_root).of(100) © ASERT 2006-2013
  • 24. …DSL example... © ASERT 2006-2013 class def def def } FluentApi { action, what the(what) { this.what = what; this } 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)
  • 25. …DSL example... Object.metaClass.please = { clos -> clos(delegate) } Object.metaClass.the = { clos -> delegate[1](clos(delegate[0])) } © ASERT 2006-2013 show = { thing -> [thing, { println it }] } square_root = { Math.sqrt(it) } given = { it } given 100 please show the square_root // ==> 10.0
  • 26. ...DSL example... show = { println it } square_root = { Math.sqrt(it) } © ASERT 2006-2013 def please(action) { [the: { what -> [of: { n -> action(what(n)) }] }] } please show the square_root of 100 // ==> 10.0
  • 27. ...DSL example... show = { println it } square_root = { Math.sqrt(it) } © ASERT 2006-2013 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 …
  • 28. ...DSL example // Japanese DSL using GEP3 rules Object.metaClass.を = Object.metaClass.の = { clos -> clos(delegate) } © ASERT 2006-2013 まず = { 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
  • 29. Topics © ASERT 2006-2013 • Introduction to DSLs • Introduction to Groovy • DSLs in Groovy Why Groovy? • Tortoise & Crane Example • Einstein’s Riddle • Further Discussion • More Info
  • 30. 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
  • 31. Compile-time Metaprogramming © ASERT 2006-2013 Transformation
  • 32. 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; } © ASERT 2006-2013 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); } // ...
  • 33. …Better Design Patterns: Delegate… import java.util.Date; public class Event { private String title; private String url; private Date when; boilerplate public String getUrl() { return url; } © ASERT 2006-2013 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); } // ...
  • 34. …Better Design Patterns: Delegate class Event { String title, url @Delegate Date when } © ASERT 2006-2013
  • 35. …Better Design Patterns: Delegate class Event { String title, url @Delegate Date when } © ASERT 2006-2013 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)
  • 36. @Immutable... • Java Immutable Class – As per Joshua Bloch Effective Java // ... @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; } public final class Person { private final String first; private final String last; © ASERT 2006-2013 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 String toString() { return "Person(first:" + first + ", last:" + last + ")"; } }
  • 37. ...@Immutable... • Java Immutable Class boilerplate – As per Joshua Bloch Effective Java // ... @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; } public final class Person { private final String first; private final String last; © ASERT 2006-2013 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 String toString() { return "Person(first:" + first + ", last:" + last + ")"; } }
  • 38. ...@Immutable © ASERT 2006-2013 @Immutable class Person { String first, last }
  • 39. AST Transforms © ASERT 2006-2013 • • • • • • • • • • • • • • • @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
  • 40. Drools Expert Golfing Example… /* * Copyright 2010 JBoss Inc * Licensed under the Apache License… */ package org.drools.examples.golfing; import import import import import import import // ... ksession.fireAllRules(); ksession.dispose(); } org.drools.KnowledgeBase; org.drools.KnowledgeBaseFactory; org.drools.builder.KnowledgeBuilder; org.drools.builder.KnowledgeBuilderFactory; org.drools.builder.ResourceType; org.drools.io.ResourceFactory; org.drools.runtime.StatefulKnowledgeSession; 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; } 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); /** * @return the color */ public String getColor() { return this.color; } final KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase(); kbase.addKnowledgePackages(kbuilder.getKnowledgePackages()); final StatefulKnowledgeSession ksession = kbase.newStatefulKnowledgeSession(); /** * @return the name */ public String getName() { return this.name; } 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])); } } } // ... /** * @return the name */ public int getPosition() { return this.position; } } }
  • 41. …Drools Expert Golfing Example import import import import import groovy.transform.Immutable static org.drools.builder.ResourceType.DRL static org.drools.KnowledgeBaseFactory.newKnowledgeBase static org.drools.builder.KnowledgeBuilderFactory.newKnowledgeBuilder 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 }
  • 42. …Drools Expert Golfing Example import import import import import groovy.transform.Immutable static org.drools.builder.ResourceType.DRL static org.drools.KnowledgeBaseFactory.newKnowledgeBase static org.drools.builder.KnowledgeBuilderFactory.newKnowledgeBuilder 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 }
  • 43. …Drools Expert Golfing Example import import import import import groovy.transform.Immutable static org.drools.builder.ResourceType.DRL static org.drools.KnowledgeBaseFactory.newKnowledgeBase static org.drools.builder.KnowledgeBuilderFactory.newKnowledgeBuilder 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 }
  • 44. @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 import import import import groovy.transform.Immutable org.drools.builder.ResourceType static org.drools.KnowledgeBaseFactory.newKnowledgeBase static org.drools.builder.KnowledgeBuilderFactory.newKnowledgeBuilder static org.drools.io.ResourceFactory.newClassPathResource def kbuilder = newKnowledgeBuilder() // ...
  • 45. …@Grab… Old school > groovy -classpath C:ProjectsGroovyProblemSolversoutproductionDroolsExpert; C:Userspaulk.groovygrapesorg.droolsdrools-compilerjarsdrools-compiler-5.3.3.Final.jar; C:Userspaulk.groovygrapesorg.antlrantlr-runtimejarsantlr-runtime-3.3.jar; C:Userspaulk.groovygrapesorg.antlrantlrjarsantlr-3.3.jar; C:Userspaulk.groovygrapesorg.antlrstringtemplatejarsstringtemplate-3.2.1.jar; C:Userspaulk.groovygrapesantlrantlrjarsantlr-2.7.7.jar; C:Userspaulk.groovygrapesorg.eclipse.jdt.core.compilerecjjarsecj-3.5.1.jar; C:Userspaulk.groovygrapesorg.mvelmvel2jarsmvel2-2.1.0.drools16.jar; C:Userspaulk.groovygrapescom.sun.xml.bindjaxb-xjcjarsjaxb-xjc-2.2.5.jboss-1.jar; C:Userspaulk.groovygrapescom.sun.xml.bindjaxb-impljarsjaxb-impl-2.2.5.jboss-1.jar; C:Userspaulk.groovygrapesjavax.xml.bindjaxb-apijarsjaxb-api-2.2.6.jar; C:Userspaulk.groovygrapescom.sun.istackistack-commons-runtimejarsistack-commons-runtime-2.6.1.jar; C:Userspaulk.groovygrapesjavax.xml.streamstax-apijarsstax-api-1.0-2.jar; C:Userspaulk.groovygrapesjavax.activationactivationjarsactivation-1.1.jar; C:Userspaulk.groovygrapescom.sun.xml.txw2txw2jarstxw2-20110809.jar; C:Userspaulk.groovygrapesrelaxngDatatyperelaxngDatatypejarsrelaxngDatatype-20020414.jar; C:Userspaulk.groovygrapescom.sun.codemodelcodemodeljarscodemodel-2.6.jar; C:Userspaulk.groovygrapescom.sun.xml.dtd-parserdtd-parserjarsdtd-parser-1.1.jboss-1.jar; C:Userspaulk.groovygrapescom.sun.istackistack-commons-toolsjarsistack-commons-tools-2.6.1.jar; C:Userspaulk.groovygrapesorg.apache.antantjarsant-1.7.0.jar; C:Userspaulk.groovygrapesorg.apache.antant-launcherjarsant-launcher-1.7.0.jar; C:Userspaulk.groovygrapesorg.kohsuke.rngomrngomjarsrngom-201103.jboss-1.jar; C:Userspaulk.groovygrapescom.sun.xsomxsomjarsxsom-20110809.jar; C:Userspaulk.groovygrapesxml-resolverxml-resolverjarsxml-resolver-1.1.jar; C:Userspaulk.groovygrapesorg.droolsdrools-corejarsdrools-core-5.3.3.Final.jar; C:Userspaulk.groovygrapesorg.droolsknowledge-apijarsknowledge-api-5.3.3.Final.jar; C:ProjectsGroovyProblemSolversDroolsExpertsrcresources GolfExample.groovy Or you can precompile: > groovyc -classpath ... GolfExample.groovy > jar ... Then use groovy or java commands to run.
  • 46. …@Grab… With @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 import import import import groovy.transform.Immutable org.drools.builder.ResourceType static org.drools.KnowledgeBaseFactory.newKnowledgeBase static org.drools.builder.KnowledgeBuilderFactory.newKnowledgeBuilder static org.drools.io.ResourceFactory.newClassPathResource def kbuilder = newKnowledgeBuilder() // ... > groovy GolfExample.groovy
  • 47. …@Grab With @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 import import import import groovy.transform.Immutable org.drools.builder.ResourceType static org.drools.KnowledgeBaseFactory.newKnowledgeBase static org.drools.builder.KnowledgeBuilderFactory.newKnowledgeBuilder static org.drools.io.ResourceFactory.newClassPathResource def kbuilder = newKnowledgeBuilder() // ... > groovy GolfExample
  • 48. Topics © ASERT 2006-2013 • Introduction to DSLs • Introduction to Groovy • DSLs in Groovy • Why Groovy? Tortoise & Crane Example • Einstein’s Riddle • Further Discussion • More Info
  • 49. 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
  • 50. 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() Found a solution: 4 * Cranes 3 * Tortoises 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() }
  • 51. …Tortoises & Cranes: Choco import import import import static choco.Choco.* choco.cp.model.CPModel choco.cp.solver.CPSolver 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
  • 52. Tortoises & Cranes: Simplistic… import import import import import groovy.transform.Immutable org.drools.builder.ResourceType static org.drools.KnowledgeBaseFactory.newKnowledgeBase static org.drools.builder.KnowledgeBuilderFactory.newKnowledgeBuilder 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()
  • 53. …Tortoises & Cranes: Simplistic… import import import import import groovy.transform.Immutable… org.drools.builder.ResourceType static org.drools.KnowledgeBaseFactory.newKnowledgeBase (numAnimals + 1).times { n -> static org.drools.builder.KnowledgeBuilderFactory.newKnowledgeBuilder if (numLegs.intdiv(Crane.numLegs) >= n) { static org.drools.io.ResourceFactory.newReaderResource ksession.insert(new Crane(n)) } def numAnimals = 7 if (numLegs.intdiv(Tortoise.numLegs) >= n) { def numLegs = 20 ksession.insert(new Tortoise(n)) def kbuilder = newKnowledgeBuilder() } kbuilder.add(newReaderResource(new StringReader(''' } dialect "mvel" rule "deduce animal counts" ksession.fireAllRules() when ksession.dispose() $crane : Crane( ) $tortoise : Tortoise( @Immutable quantity + $crane.quantity == ''' + numAnimals + ''', class Crane { quantity * numLegs + $crane.quantity * $crane.numLegs == ''' + numLegs + ''' static int numLegs = 2 ) int quantity then } System.out.println( "Cranes " + $crane.getQuantity() ) System.out.println( "Tortoises " + $tortoise.getQuantity() ) @Immutable end class Tortoise { ''')), ResourceType.DRL) static int numLegs = 4 def kbase = newKnowledgeBase() int quantity kbase.addKnowledgePackages(kbuilder.knowledgePackages) } def ksession = kbase.newStatefulKnowledgeSession()
  • 54. …Tortoises & Cranes: Simplistic import import import import import groovy.transform.Immutable… org.drools.builder.ResourceType static org.drools.KnowledgeBaseFactory.newKnowledgeBase (numAnimals + 1).times { n -> static org.drools.builder.KnowledgeBuilderFactory.newKnowledgeBuilder if (numLegs.intdiv(Crane.numLegs) >= n) { static org.drools.io.ResourceFactory.newReaderResource ksession.insert(new Crane(n)) } def numAnimals = 7 if (numLegs.intdiv(Tortoise.numLegs) >= n) { def numLegs = 20 ksession.insert(new Tortoise(n)) def kbuilder = newKnowledgeBuilder() } kbuilder.add(newReaderResource(new StringReader(''' } dialect "mvel" rule "deduce animal counts" ksession.fireAllRules() when ksession.dispose() $crane : Crane( ) $tortoise : Tortoise( @Immutable quantity + $crane.quantity == ''' + numAnimals + ''', class Crane { quantity * numLegs + $crane.quantity * $crane.numLegs == ''' + numLegs + ''' static int numLegs = 2 ) int quantity then What is the impact } System.out.println( "Cranes " + $crane.getQuantity() ) of adding another System.out.println( "Tortoises " + $tortoise.getQuantity() ) @Immutable end kind of animal? class Tortoise { ''')), ResourceType.DRL) static int numLegs = 4 def kbase = newKnowledgeBase() int quantity kbase.addKnowledgePackages(kbuilder.knowledgePackages) } def ksession = kbase.newStatefulKnowledgeSession()
  • 55. 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 import import import import groovy.transform.Field org.drools.builder.ResourceType static org.drools.KnowledgeBaseFactory.* static org.drools.builder.KnowledgeBuilderFactory.* 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' } } …
  • 56. …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() } } …
  • 57. …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) }
  • 58. …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
  • 59. Topics © ASERT 2006-2013 • Introduction to DSLs • Introduction to Groovy • DSLs in Groovy • Why Groovy? • Tortoise & Crane Example Einstein’s Riddle • Further Discussion • More Info
  • 60. Einstein’s Riddle… • Wikipedia: The zebra puzzle is a wellknown logic puzzle © ASERT 2006-2013 – 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
  • 61. …Einstein’s Riddle • Some premises: © ASERT 2006-2013 – – – – – – – – – – – – – – – 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?
  • 62. Einstein’s Riddle : Prolog % from http://www.baptiste-wicht.com/2010/09/solve-einsteins-riddle-usingprolog % 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). © ASERT 2006-2013 % 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.
  • 63. Einstein’s Riddle : Polyglot @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.* © ASERT 2006-2013 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
  • 64. Einstein’s Riddle : Polyglot w/ DSL… // 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 } © ASERT 2006-2013 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). ''')
  • 65. …Einstein’s Riddle : Polyglot w/ DSL… // 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++ } © ASERT 2006-2013 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++ } ...
  • 66. …Einstein’s Riddle : Polyglot w/ DSL… © ASERT 2006-2013 // 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]) } ] } }]} ] } ...
  • 67. …Einstein’s Riddle : Polyglot w/ DSL… © ASERT 2006-2013 // 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
  • 68. …Einstein’s Riddle : Polyglot w/ DSL… // 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]) }, ... ] } ... © ASERT 2006-2013 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])
  • 69. …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: © ASERT 2006-2013 • But other parts are not known, e.g. the word ‘house’ in the fragment below: ‘house’ is key for a Map and could be any value
  • 70. …Einstein’s Riddle : Polyglot w/ DSL def the(Color c1) {[ house: { _is -> [on: { _the -> [left: { _side -> [of: { __the -> Color.values().collectEntries{ c2 -> [c2.toString(), { _dummy -> addToLeftHint([color:c1], [color:c2]) }]} }]}]}]} ]} © ASERT 2006-2013 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) } ‘house’ is now understood We can choose to introduce additional static typing information into our DSL implementation or ‘teach’ our IDE about or DSL.
  • 71. Einstein’s Riddle : Choco DSL… //@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 © ASERT 2006-2013 enum enum enum enum enum Pet { dog, cat, bird, fish, horse } Color { green, white, red, blue, yellow } Sport { baseball, volleyball, football, hockey, tennis } Drink { water, tea, milk, coffee, beer } Nationality { Norwegian, Dane, Briton, German, Swede } import import import import import static static static static static Pet.* Color.* Sport.* Drink.* Nationality.* // define logic solver data structures num = 5 center = 2 first = 0 println "Solving Einstein's Riddle:" …
  • 72. …Einstein’s Riddle : Choco DSL… © ASERT 2006-2013 … @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 pets[i] = colors[i] = plays[i] = drinks[i] = nations[i] = } { i -> makeEnumVar("pet$i", makeEnumVar("color$i", makeEnumVar("plays$i", makeEnumVar("drink$i", makeEnumVar("nation$i", pets) colors) plays) drinks) nations) def pretty(s, c, arr, i) { c.values().find{ it.ordinal() == s.getVar(arr[i])?.value } } …
  • 73. …Einstein’s Riddle : Choco DSL… © ASERT 2006-2013 … // 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' …
  • 74. …Einstein’s Riddle : Choco DSL… © ASERT 2006-2013 … // 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) } ] } }]} ] } …
  • 75. …Einstein’s Riddle : Choco DSL… … 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)) }) }]} }]}]}]} ]} © ASERT 2006-2013 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) } ]}]}]} ] }, …
  • 76. …Einstein’s Riddle : Choco DSL… … © ASERT 2006-2013 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) } ] } …
  • 77. …Einstein’s Riddle : Choco DSL © ASERT 2006-2013 … // 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 …
  • 78. …Einstein’s Riddle : Choco… © ASERT 2006-2013 … // 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() }
  • 79. Einstein’s Riddle : Output 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 © ASERT 2006-2013
  • 80. Topics © ASERT 2006-2013 • Introduction to DSLs • Introduction to Groovy • DSLs in Groovy • Why Groovy? • Tortoise & Crane Example • Einstein’s Riddle Further Discussion • More Info
  • 81. Discussion points • • • • Choosing granularity Choosing the level of dynamic/static typing Multi-paradigm solutions Capturing Rule Design Patterns using AST transforms
  • 82. 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 different The Guest different ... at position 2 gender to the at position 2 gender to the on table Guest at on table Guest at 1 should position 1 should position have a 1 have a 3
  • 83. Typing… • Dynamic • Traditional Static Typing • Stronger levels of Static Typing
  • 84. …Typing… 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. 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'
  • 85. …Typing… 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 } } 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) }
  • 86. …Typing package tictactoe enum Position { NW, N, NE, W, C, E, SW, S, SE } 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. 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) } [Static type checking] - Attempt to call suboptimal move SE not allowed [HINT: try NE]
  • 87. 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
  • 88. 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
  • 89. Topics © ASERT 2006-2013 • Introduction to DSLs • Introduction to Groovy • DSLs in Groovy • Why Groovy? • Tortoise & Crane Example • Einstein’s Riddle • Further Discussion More Info
  • 90. More Information: URLs • Groovy – http://groovy.codehaus.org/ • Groovy DSL talk in general – http://www.slideshare.net/glaforge/groovy-domain-specificlanguages-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/
  • 91. More Information: Groovy in Action 2ed Contains a chapter on DSLs!